Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách Zoom In/Zoom Out ImageView trên Android bằng Kotlin

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 GestureDetector kết hợp cho double-tap zoom.
  • Xử lý orientation change: lưu/restore scaleFactor trong onSaveInstanceState.