自定义一个支持内容缩放,移动预览的ImageView

1,226 阅读2分钟

在自己做的一个app中,遇到了一个需求,图片需要支持内容缩放,内容移动预览,选择了PhotoView来实现该功能,但是PhotoView使用centorCrop有一个内容裁剪的缺点,所以尝试使用自定义ImageView来实现,下面直接上代码

 * Author: Sean-Shen
 * Date: 2021/1/19
 * Desc:
 */
import android.content.Context
import android.graphics.Matrix
import android.graphics.RectF
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import androidx.appcompat.widget.AppCompatImageView
import com.blankj.utilcode.util.LogUtils
import kotlin.math.abs

class MyImageView : AppCompatImageView {
    private var oldX: Float = 0f
    private var oldY: Float = 0f
    private var mMatrix: Matrix? = null
    private var initScale: Float = 1.0f   //初始缩放值
    private var totalScale: Float = 1.0f  //放大时的缩放值

    //缩放的优先级,比拖动的优先级高
    private var mScaleGestureDetector: ScaleGestureDetector? = null

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

    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
        initImageView()
    }

    constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(
        context,
        attributeSet,
        defStyleAttr
    ) {
        initImageView()
    }

    /**
     * 初始化一些配置
     */
    private fun initImageView() {
        mScaleGestureDetector =
            ScaleGestureDetector(context, object : ScaleGestureDetector.OnScaleGestureListener {
                override fun onScale(detector: ScaleGestureDetector?): Boolean {
                    //要进行边界检查
                    detector?.scaleFactor?.let {
                        totalScale += it - initScale
                        LogUtils.e("Sean--->totalScale$totalScale")
                        scaleImg(it)
                    }

                    return true   //返回false,持续计算放大比例,返回true,重置放大比例
                }

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

                override fun onScaleEnd(detector: ScaleGestureDetector?) {
                }

            })
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        val tempRectF = getMatrixRectF()
        LogUtils.e("Sean--->${tempRectF.right} + ${tempRectF.left} + ${tempRectF.bottom} + ${tempRectF.top} + width:$width + height$height")
        centerLayout()
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        mScaleGestureDetector?.onTouchEvent(event)
        event?.let {
            when (it.action) {
                MotionEvent.ACTION_DOWN -> {
                    oldX = it.x
                    oldY = it.y
                }

                MotionEvent.ACTION_MOVE -> {
                    if (abs((it.x - oldX).toInt()) > 2 || abs((it.y - oldY).toInt()) > 2) {
                        translateBitmap(it.x - oldX, it.y - oldY)
                        oldX = it.x
                        oldY = it.y
                        cancelLongPress()   //如果是移动事件,屏蔽长按事件,解决冲突
                    }

                }

                MotionEvent.ACTION_UP -> {   //处理边界回弹
                    handleBorder()
                }

                else -> return@let

            }
        }
        return super.onTouchEvent(event)
    }

    /**
     * 进行图片缩放
     */
    private fun scaleImg(scaleFactor: Float) {
        //放大时不用进行边界检查
        if (totalScale in 1.0f..2.0f) {
            scaleBitmap(scaleFactor, scaleFactor)
        } else if (totalScale <= 1.0f) {
            //缩小时,需要处理边界问题
            handleScaleBorder()
        }
    }

    /**
     * 缩放图片
     */
    private fun scaleBitmap(xScale: Float, yScale: Float) {
        mMatrix?.postScale(xScale, yScale)
        imageMatrix = mMatrix
    }

    /**
     * 平移图片
     */
    private fun translateBitmap(offsetX: Float, offsetY: Float) {
        mMatrix?.postTranslate(offsetX, offsetY)
        imageMatrix = mMatrix
    }

    /**
     * 处理边界回弹
     */
    private fun handleBorder() {
        var tmpOffsetX = 0f
        var tmpOffsetY = 0f
        val tempRectF = getMatrixRectF()
        if (tempRectF.left > 0) {   //左侧向右,左侧存在空白
            tmpOffsetX = -tempRectF.left
        }

        if (tempRectF.right < this.width) {  //右侧向左,右侧存在白边
            tmpOffsetX = this.width - tempRectF.right
        }

        if (tempRectF.top > 0) {  //上侧向下,上侧存在白边
            tmpOffsetY = -tempRectF.top
        }

        if (tempRectF.bottom < this.height) {   //下侧向上,下侧存在白边
            tmpOffsetY = this.height - tempRectF.bottom
        }

        translateBitmap(tmpOffsetX, tmpOffsetY)
    }

    /**
     * 处理缩小时的边界问题
     */
    private fun handleScaleBorder() {
        val tempRectF = getMatrixRectF()
        if (width > height && tempRectF.left <= 0) {
            val tempScaleFactor = width.toFloat() / (tempRectF.right - tempRectF.left)
            scaleBitmap(tempScaleFactor, tempScaleFactor)
        } else if (height >= width && tempRectF.top <= 0) {
            val tempScaleFactor = height.toFloat() / (tempRectF.bottom - tempRectF.top)
            scaleBitmap(tempScaleFactor, tempScaleFactor)
        }
    }

    /**
     * 获取变化之后的图片尺寸
     */
    private fun getMatrixRectF(): RectF {
        val rectF = RectF()
        val mDrawable = drawable
        mDrawable?.let {
            rectF.set(0f, 0f, it.intrinsicWidth.toFloat(), it.intrinsicHeight.toFloat())
            mMatrix?.mapRect(rectF)
        }
        return rectF
    }

    /**
     * 将图片中间区域显示在View中
     */
    private fun centerLayout() {
        val tempRectF = getMatrixRectF()
        val offsetX = -(tempRectF.right - tempRectF.left - width) / 2
        val offsetY = -(tempRectF.bottom - tempRectF.top - height) / 2
        val matrix = Matrix()

        matrix.postTranslate(offsetX, offsetY)
        mMatrix = matrix   //要更新这个mMatrix
        imageMatrix = matrix
    }

    /**
     * 获取当前的缩放值
     */
    private fun getScale(): Float {
        val matrixValues = FloatArray(9)
        matrix.getValues(matrixValues)
        return matrixValues[Matrix.MSCALE_X]
    }

}