Giới thiệu
Hướng dẫn này trình bày cách triển khai chức năng zoom in/zoom out (phóng to/thu nhỏ) cho ImageView trên Android sử dụng ScaleGestureDetector và Kotlin. Hỗ trợ pinch-to-zoom bằng 2 ngón tay.
Các bước thực hiện
Bước 1: Tạo dự án mới
Tạo project: File → New Project.
Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix"
android:src="@drawable/image" />
</LinearLayout>
Lưu ý: android:scaleType="matrix" bắt buộc để cho phép transform bằng matrix.
Bước 3: Chuẩn bị hình ảnh
Copy một file ảnh (jpg/png) vào res/drawable đặt tên image.jpg.
Bước 4: Xử lý logic (src/MainActivity.kt)
import android.os.Bundle
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import kotlin.math.max
import kotlin.math.min
class MainActivity : AppCompatActivity() {
private lateinit var scaleGestureDetector: ScaleGestureDetector
private var scaleFactor = 1.0f
private lateinit var imageView: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "KotlinApp"
imageView = findViewById(R.id.imageView)
scaleGestureDetector = ScaleGestureDetector(this, ScaleListener())
}
override fun onTouchEvent(event: MotionEvent): Boolean {
scaleGestureDetector.onTouchEvent(event)
return true // Quan trọng: return true để nhận tiếp các event
}
private inner class ScaleListener : SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
scaleFactor *= detector.scaleFactor
// Giới hạn scale từ 0.1x đến 10x
scaleFactor = max(0.1f, min(scaleFactor, 10.0f))
imageView.scaleX = scaleFactor
imageView.scaleY = scaleFactor
return true
}
}
}
Bước 5: Cấu hình Manifest (androidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="app.com.q11">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Chạy ứng dụng
Kết nối thiết bị, nhấn Run. Sử dụng 2 ngón tay pinch in/out trên ImageView để zoom in/out.
Giải thích
ScaleGestureDetector: Phát hiện cử chỉ pinch (2 ngón).detector.scaleFactor: Tỷ lệ scale của cử chỉ hiện tại ( >1 zoom in, <1 zoom out).scaleX/scaleY: Scale View theo trục X/Y.max/min: Giới hạn zoom tránh quá nhỏ/lớn.
Cải tiến: Zoom + Pan (Kéo thả) hoàn chỉnh
Code trên chỉ zoom từ trung tâm. Để zoom tại điểm chạm + kéo ảnh, cần dùng Matrix:
class ZoomableImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
private val matrix = Matrix()
private val savedMatrix = Matrix()
private var mode = 0
private val scaleGestureDetector = ScaleGestureDetector(context, ScaleListener())
companion object {
const val NONE = 0
const val DRAG = 1
const val ZOOM = 2
}
override fun onTouchEvent(event: MotionEvent): Boolean {
scaleGestureDetector.onTouchEvent(event)
when (event.action & MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
savedMatrix.set(matrix)
mode = DRAG
}
MotionEvent.ACTION_POINTER_DOWN -> {
mode = ZOOM
}
MotionEvent.ACTION_MOVE -> {
if (mode == DRAG) {
matrix.set(savedMatrix)
matrix.postTranslate(event.x - savedMatrix[Matrix.MTRANS_X], event.y - savedMatrix[Matrix.MTRANS_Y])
} else if (mode == ZOOM) {
// Xử lý zoom tại điểm giữa 2 ngón
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> mode = NONE
}
imageMatrix = matrix
return true
}
inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scale = detector.scaleFactor.coerceIn(0.1f, 10.0f)
matrix.postScale(scale, scale, detector.focusX, detector.focusY)
return true
}
}
}
Thư viện khuyên dùng
Thay vì tự viết, hãy dùng thư viện đã tối ưu:
- PhotoView (Chris Banes): `implementation "com.github.chrisbanes:PhotoView:2.3.0"`
- TouchImageView: Single file, dễ tích hợp.
- Coil/Glide/Picasso + PhotoView cho load ảnh network + zoom.
Lưu ý
scaleType="matrix"bắt buộc cho custom matrix/scale.- Trên Android 10+, cân nhắc dùng
GestureDetectorkết hợp cho double-tap zoom. - Xử lý orientation change: lưu/restore scaleFactor trong onSaveInstanceState.