如何用Kotlin在Android中实现ImageView的自定义缩放功能

455 阅读3分钟

用Kotlin在Android中实现ImageView的自定义缩放功能

缩放是一种运动中的操作,用于放大或缩小Android应用程序中的图像或物体的大小。它为用户提供了一个强大而吸引人的视觉效果。

前提条件

为了更好地学习本教程,读者将需要以下条件。

  • 确保你的电脑上安装了Android Studio。
  • 需要对Kotlin编程语言的基本概念有所了解。
  • 具备Matrices的基本数学知识。
  • 具备创建和运行Android应用程序的基本知识。

安卓系统中缩放的重要性

  • 它能让人看到不集中在当前屏幕上的图像。
  • 缩放控制允许用户对屏幕上所有大大小小的物体进行清晰的查看。
  • 缩放提供了一种方式,用户可以集中在整个图像的一个指定部门并研究它。

创建一个自定义的缩放效果

为了成功地创建一个自定义的缩放动作,应该实施几种方法。

首先,让我们创建一个扩展于AppCompatImageView 的类,并实现以下内容。

  • View.OnTouchListener
  • GestureDetector.OnGestureListener
  • GestureDetector.OnDoubleTapListener

在这个类中,确保你已经声明了以下变量,这些变量有助于创建缩放控件。

var myMatrix: Matrix? = null
private var matrixValue: FloatArray? = null
var mode = NONE // import this constant from the View class

// Scales
var presentScale = 1f
var minimumScale = 1f
var maximumScale = 4f

//Dimensions
var originalWidth = 0f
var originalHeight = 0f
var viewedWidth = 0
var viewedHeight = 0

在代码结构中包括以下构造函数的细节。

constructor(context: Context) : super(context) {
        constructionDetails(context)
}

constructor(context: Context, @Nullable attrs: AttributeSet?) : super(context, attrs) {
    constructionDetails(context)
}

constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
    context!!, attrs, defStyleAttr
)

创建以下方法,作为创建缩放功能的基础。

private fun constructionDetails(context: Context){
    super.setClickable(true)

    myContext = context
    myScaleDetector = ScaleGestureDetector(context,ScalingListener())
    myMatrix = Matrix()
    matrixValue = FloatArray(10)
    imageMatrix = myMatrix
    scaleType = ScaleType.MATRIX
    myGestureDetector = GestureDetector(context, this)
    setOnTouchListener(this)
}

确保你已经将myScaleDetector,myGestureDetectormyContext 声明为全局变量。

为了检索ScaleGestureDetector ,它通常是一个接口,我们将创建一个内部类并实现,ScaleGestureDetector.SimpleOnScaleGestureListener 。然后这个类将作为尺度手势检测器。

private inner class ScalingListener : ScaleGestureDetector.SimpleOnScaleGestureListener(){
    ...
}

这个类重写了两个方法,onScaleBeginonScale ,其中包含了ScaleGestureDetector 接口的一般实现。

onScaleBegin 显示了应该进行的缩放的模式级别。

override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
    zoomMode = 2
    return true
}

onScale 方法将原始图片尺寸转换为一个给定的比例因子。下面的代码演示了如何完整地使用onScale 函数。

override fun onScale(detector: ScaleGestureDetector): Boolean {
    var mScaleFactor = detector.scaleFactor
    val previousScale = mScaleFactor
    presentScale *= mScaleFactor
    if (presentScale > maximumScale) {
        presentScale = maximumScale
        mScaleFactor = maximumScale / previousScale
    } else if (presentScale < minimumScale) {
        presentScale = minimumScale
        mScaleFactor = minimumScale / previousScale
    }
    if (originalWidth * presentScale <= mViewedWidth
        || originalHeight * presentScale <= mViewedHeight
    ) {
        myMatrix!!.postScale(
            mScaleFactor, mScaleFactor, mViewedWidth / 2.toFloat(),
            mViewedHeight / 2.toFloat()
        )
    } else {
        myMatrix!!.postScale(
            mScaleFactor, mScaleFactor,
            detector.focusX, detector.focusY
        )
    }
    fittedTranslation()
    return true
}    

下面显示的方法用于固定过渡,并将矩阵值放在一个数组内进行分析。

fun fittedTranslation() {
    myMatrix!!.getValues(matrixValue) // get matrix values
    val translationX = matrixValue!![Matrix.MTRANS_X]
    val translationY = matrixValue!![Matrix.MTRANS_Y]

    val fittedTransX = getFittedTranslation(translationX, mViewedWidth.toFloat(), originalWidth * presentScale) // get fitted translation
    val fittedTransY = getFittedTranslation(translationY, mViewedHeight.toFloat(), originalHeight * presentScale)

    if (fittedTransX != 0f || fittedTransY != 0f) myMatrix!!.postTranslate(fittedTransX, fittedTransY) // post fitted translation
}

另一个重要的方法是用来根据图像的坐标和所提供的比例系数将其装入屏幕,如下图所示。

private fun putToScreen() {
    availableSCale = 1f
    val factor: Float
    val mDrawable = drawable
    // return if there is no drawable or thee dimensions are 0
    if (mDrawable == null || mDrawable.intrinsicWidth == 0 || mDrawable.intrinsicHeight == 0) return

    val mImageWidth = mDrawable.intrinsicWidth
    val mImageHeight = mDrawable.intrinsicHeight
    val factorX = mViewedWidth.toFloat() / mImageWidth.toFloat()
    val factorY = mViewedHeight.toFloat() / mImageHeight.toFloat()

    factor = factorX.coerceAtMost(factorY)
    myMatrix!!.setScale(factor, factor)
    
    // Centering the image
    var repeatedYSpace = (mViewedHeight.toFloat() - factor * mImageHeight.toFloat())
    var repeatedXSpace = (mViewedWidth.toFloat() - factor * mImageWidth.toFloat())
    repeatedYSpace /= 2.toFloat()
    repeatedXSpace /= 2.toFloat()
    myMatrix!!.postTranslate(repeatedXSpace, repeatedYSpace)
    originalWidth = mViewedWidth - 2 * repeatedXSpace
    originalHeight = mViewedHeight - 2 * repeatedYSpace
    imageMatrix = myMatrix
}

getFittedTranslation 是一个处理图像的负坐标和图像未被缩放的情况的方法。

下面的片段显示了它的实现。

private fun getFittedTranslation(mTranslate: Float,vSize: Float, cSize: Float): Float {
    val minimumTranslation: Float
    val maximumTranslation: Float
    if (cSize <= vSize) { // case: NOT ZOOMED
        minimumTranslation = 0f
        maximumTranslation = vSize - cSize
    } else { //CASE: ZOOMED
        minimumTranslation = vSize - cSize
        maximumTranslation = 0f
    }
    if (mTranslate < minimumTranslation) {
        return -mTranslate + minimumTranslation
    }
    if (mTranslate > maximumTranslation) {
        return -mTranslate + maximumTranslation
    }

    return 0F
}

最后,我们将实现以下onTouch 缩放控制方法。这个方法用于处理触摸事件。

override fun onTouch(mView: View, mMouseEvent: MotionEvent): Boolean {
        myScaleDetector!!.onTouchEvent(mMouseEvent)
        myGestureDetector!!.onTouchEvent(mMouseEvent)
        val currentPoint = PointF(mMouseEvent.x, mMouseEvent.y)

        val mDisplay = this.display
        val mLayoutParams = this.layoutParams
        mLayoutParams.width = mDisplay.width
        mLayoutParams.height = mDisplay.height
        this.layoutParams = mLayoutParams

        when (mMouseEvent.action) {
            MotionEvent.ACTION_DOWN -> {
                lastPoint.set(currentPoint)
                startPoint.set(lastPoint)
                zoomMode = 1
            }
            MotionEvent.ACTION_MOVE -> if (zoomMode == 1) {
                val changeInX = currentPoint.x - lastPoint.x
                val changeInY = currentPoint.y - lastPoint.y
                val fixedTranslationX = getFixDragTrans(changeInX, viewedWidth.toFloat(), originalWidth * availableSCale)
                val fixedTranslationY = getFixDragTrans(changeInY, viewedHeight.toFloat(), originalHeight * availableSCale)
                myMatrix!!.postTranslate(fixedTranslationX, fixedTranslationY)
                fittedTranslation()
                lastPoint[currentPoint.x] = currentPoint.y
            }
            MotionEvent.ACTION_POINTER_UP -> zoomMode = 0
        }
        imageMatrix = myMatrix
        return false
}

现在我们已经构建了自定义的缩放功能,我们需要知道它在哪里适用,以便它能发挥作用。在你的XML文件中的ImageView ,将ImageView 替换为下面所示的包名称。

<com.odhiambodevelopers.mycustomzoomdemo.CustomZoom
        android:id="@+id/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/robot"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

注意:你的包的名称可能与上面显示的不同。

演示

运行该应用程序并尝试放大和缩小。它应该如下图所示工作。

Image not zoomed

Zoomed image

结论

在本教程中,我们已经了解了什么是缩放,以及如何使用Kotlin在Android的ImageView中实现自定义缩放功能。