PreviewImageView

719 阅读1分钟

图片预览组件

支持缩放、平移手势、双击缩放

知识点:

  • AppCompatImageView
  • AppCompatImageView#setImageMatrix
  • Matrix
  • matrix.mapRect(mResultRectF, mRectF)
  • GestureDetector
  • ScaleGestureDetector

1.继承AppCompatImageView

class PreviewImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {}

2.设置加载模式ScaleType.MATRIX

    init {
        scaleType = ScaleType.MATRIX
    }

3.初始化缩放平移手势

/**
 * 滑动双击手势
 */
private val mGestureDetector by lazy {
    GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
        override fun onScroll(
            e1: MotionEvent,
            e2: MotionEvent,
            distanceX: Float,
            distanceY: Float
        ): Boolean {
            mGestureMatrix.postTranslate(-distanceX, -distanceY)
            imageMatrix = mGestureMatrix
            return super.onScroll(e1, e2, distanceX, distanceY)
        }

        override fun onDoubleTap(e: MotionEvent): Boolean {
            val x = e.x
            val y = e.y
            if (currentScale < DOUBLE_SCALE) {
                mGestureMatrix.postScale(
                    DOUBLE_SCALE_2MAX / currentScale,
                    DOUBLE_SCALE_2MAX / currentScale,
                    x,
                    y
                )
                currentScale = DOUBLE_SCALE_2MAX
            } else {
                mGestureMatrix.postScale(
                    DOUBLE_SCALE_2MIN / currentScale,
                    DOUBLE_SCALE_2MIN / currentScale,
                    x,
                    y
                )
                currentScale = DOUBLE_SCALE_2MIN
            }
            imageMatrix = mGestureMatrix
            return super.onDoubleTap(e)
        }
    })
}

/**
 * 缩放手势
 */
private val mScaleGestureDetector by lazy {
    ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
            startScale = currentScale
            return super.onScaleBegin(detector)
        }

        override fun onScaleEnd(detector: ScaleGestureDetector?) {
            super.onScaleEnd(detector)
        }

        override fun onScale(detector: ScaleGestureDetector): Boolean {
            var scaleFactor = detector.scaleFactor * startScale
            if (scaleFactor > MAX_SCALE) {
                scaleFactor = MAX_SCALE
            } else if (scaleFactor < MIN_SCALE) {
                scaleFactor = MIN_SCALE
            }
            val dxScale = scaleFactor / currentScale
            val focusX = detector.focusX
            val focusY = detector.focusY
            mGestureMatrix.postScale(dxScale, dxScale, focusX, focusY)
            currentScale = scaleFactor
            imageMatrix = mGestureMatrix
            return super.onScale(detector)
        }
    })
}


    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        mScaleGestureDetector.onTouchEvent(event)
        mGestureDetector.onTouchEvent(event)
        return true
    }

4.重写setImageDrawable

override fun setImageDrawable(drawable: Drawable?) {
    super.setImageDrawable(drawable)
    restDrawableMatrix()
}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    restDrawableMatrix()
}

5.初始化图片位置

private fun restDrawableMatrix() {
    //不可去掉 kotlin 初始化顺序
    mGestureMatrix ?: return
    if (measuredWidth == 0 || measuredHeight == 0) {
        return
    }
    mCanShowRect.set(
        mCustomPaddingLeft,
        mCustomPaddingTop,
        measuredWidth - mCustomPaddingRight,
        measuredHeight - mCustomPaddingBottom
    )
    val width = mCanShowRect.width()
    val height = mCanShowRect.height()
    if (width <= 0 || height <= 0) {
        return
    }
    val intrinsicWidth = drawable?.intrinsicWidth ?: return
    val intrinsicHeight = drawable?.intrinsicHeight ?: return
    val x = mCanShowRect.centerX() - intrinsicWidth / 2F
    val y = mCanShowRect.centerY() - intrinsicHeight / 2F
    mGestureMatrix.reset()
    val scale =
        (width.toFloat() / intrinsicWidth).coerceAtMost(height.toFloat() / intrinsicHeight)
    mGestureMatrix.postTranslate(x, y)
    mGestureMatrix.postScale(
        scale, scale, mCanShowRect.centerX().toFloat(),
        mCanShowRect.centerY().toFloat()
    )
    mRectF.set(
        0F,
        0F,
        intrinsicWidth.toFloat(),
        intrinsicHeight.toFloat()
    )
    imageMatrix = mGestureMatrix
}

6.检查边距

/**
 * 检查边距
 */
private fun checkImageMatrix(matrix: Matrix?) {
    matrix ?: return
    matrix.mapRect(mResultRectF, mRectF)
    if (mResultRectF.width() < mCanShowRect.width()) {
        matrix.postTranslate(mCanShowRect.centerX() - mResultRectF.centerX(), 0F)
    } else {
        if (mResultRectF.right < mCanShowRect.right) {
            matrix.postTranslate(mCanShowRect.right - mResultRectF.right, 0F)
        }
        if (mResultRectF.left > mCanShowRect.left) {
            matrix.postTranslate(mCanShowRect.left - mResultRectF.left, 0F)
        }
    }
    if (mResultRectF.height() < mCanShowRect.height()) {
        matrix.postTranslate(0F, measuredHeight / 2 - mResultRectF.centerY())
    } else {
        if (mResultRectF.bottom < mCanShowRect.bottom) {
            matrix.postTranslate(0F, mCanShowRect.bottom - mResultRectF.bottom)
        }
        if (mResultRectF.top > 0) {
            matrix.postTranslate(0F, mCanShowRect.top - mResultRectF.top)
        }
    }
}

源码如下:

package com.zhanpple.jetpack.view

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.setPadding

/**
 * @author zmp
 * @date : 2021/8/26 11:50
 * des:PreviewImageView
 * 图片预览View 支持缩放、平移手势、双击缩放
 */
class PreviewImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {

    companion object {
        /**
         * 最大缩放比例
         */
        const val MAX_SCALE = 5F

        /**
         * 最小缩放比例
         */
        const val MIN_SCALE = 0.5F

        /**
         * 双击阈值
         */
        const val DOUBLE_SCALE = MAX_SCALE / 2F

        /**
         * 双击放大系数
         */
        const val DOUBLE_SCALE_2MAX = MAX_SCALE * 0.8F

        /**
         * 双击缩小系数
         */
        const val DOUBLE_SCALE_2MIN = MIN_SCALE * 1.5F
    }

    /**
     * 当前缩放比例
     */
    private var currentScale = 1F

    /**
     * 缩放手势开始的比例
     */
    private var startScale = 1F

    /**
     * 预览图片左边距
     */
    private var mCustomPaddingLeft = 0

    /**
     * 预览图片右边距
     */
    private var mCustomPaddingRight = 0

    /**
     * 预览图片上边距
     */
    private var mCustomPaddingTop = 0

    /**
     * 预览图片下边距
     */
    private var mCustomPaddingBottom = 0

    /**
     * 滑动双击手势
     */
    private val mGestureDetector by lazy {
        GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
            override fun onScroll(
                e1: MotionEvent,
                e2: MotionEvent,
                distanceX: Float,
                distanceY: Float
            ): Boolean {
                mGestureMatrix.postTranslate(-distanceX, -distanceY)
                imageMatrix = mGestureMatrix
                return super.onScroll(e1, e2, distanceX, distanceY)
            }

            override fun onDoubleTap(e: MotionEvent): Boolean {
                val x = e.x
                val y = e.y
                if (currentScale < DOUBLE_SCALE) {
                    mGestureMatrix.postScale(
                        DOUBLE_SCALE_2MAX / currentScale,
                        DOUBLE_SCALE_2MAX / currentScale,
                        x,
                        y
                    )
                    currentScale = DOUBLE_SCALE_2MAX
                } else {
                    mGestureMatrix.postScale(
                        DOUBLE_SCALE_2MIN / currentScale,
                        DOUBLE_SCALE_2MIN / currentScale,
                        x,
                        y
                    )
                    currentScale = DOUBLE_SCALE_2MIN
                }
                imageMatrix = mGestureMatrix
                return super.onDoubleTap(e)
            }
        })
    }

    /**
     * 缩放手势
     */
    private val mScaleGestureDetector by lazy {
        ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
            override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
                startScale = currentScale
                return super.onScaleBegin(detector)
            }

            override fun onScaleEnd(detector: ScaleGestureDetector?) {
                super.onScaleEnd(detector)
            }

            override fun onScale(detector: ScaleGestureDetector): Boolean {
                var scaleFactor = detector.scaleFactor * startScale
                if (scaleFactor > MAX_SCALE) {
                    scaleFactor = MAX_SCALE
                } else if (scaleFactor < MIN_SCALE) {
                    scaleFactor = MIN_SCALE
                }
                val dxScale = scaleFactor / currentScale
                val focusX = detector.focusX
                val focusY = detector.focusY
                mGestureMatrix.postScale(dxScale, dxScale, focusX, focusY)
                currentScale = scaleFactor
                imageMatrix = mGestureMatrix
                return super.onScale(detector)
            }
        })
    }

    /**
     * 图片原始位置
     */
    private var mRectF = RectF()

    /**
     * 图片MATRIX转换后的位置
     */
    private var mResultRectF = RectF()

    /**
     * 图片可滑动区域
     */
    private var mCanShowRect = Rect()

    /**
     * 图片转换矩阵
     */
    private val mGestureMatrix: Matrix = Matrix()

    init {
        scaleType = ScaleType.MATRIX
        //将xml padding转换成自定义的
        mCustomPaddingLeft = paddingLeft
        mCustomPaddingRight = paddingRight
        mCustomPaddingTop = paddingTop
        mCustomPaddingBottom = paddingBottom
        //清楚padding
        setPadding(0)

    }

    override fun setImageDrawable(drawable: Drawable?) {
        super.setImageDrawable(drawable)
        restDrawableMatrix()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        restDrawableMatrix()
    }

    private fun restDrawableMatrix() {
        //不可去掉 kotlin 初始化顺序
        mGestureMatrix ?: return
        if (measuredWidth == 0 || measuredHeight == 0) {
            return
        }
        mCanShowRect.set(
            mCustomPaddingLeft,
            mCustomPaddingTop,
            measuredWidth - mCustomPaddingRight,
            measuredHeight - mCustomPaddingBottom
        )
        val width = mCanShowRect.width()
        val height = mCanShowRect.height()
        if (width <= 0 || height <= 0) {
            return
        }
        val intrinsicWidth = drawable?.intrinsicWidth ?: return
        val intrinsicHeight = drawable?.intrinsicHeight ?: return
        val x = mCanShowRect.centerX() - intrinsicWidth / 2F
        val y = mCanShowRect.centerY() - intrinsicHeight / 2F
        mGestureMatrix.reset()
        val scale =
            (width.toFloat() / intrinsicWidth).coerceAtMost(height.toFloat() / intrinsicHeight)
        mGestureMatrix.postTranslate(x, y)
        mGestureMatrix.postScale(
            scale, scale, mCanShowRect.centerX().toFloat(),
            mCanShowRect.centerY().toFloat()
        )
        mRectF.set(
            0F,
            0F,
            intrinsicWidth.toFloat(),
            intrinsicHeight.toFloat()
        )
        imageMatrix = mGestureMatrix
    }


    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        mScaleGestureDetector.onTouchEvent(event)
        mGestureDetector.onTouchEvent(event)
        return true
    }

    override fun setImageMatrix(matrix: Matrix?) {
        checkImageMatrix(matrix)
        super.setImageMatrix(matrix)
    }

    /**
     * 检查边距
     */
    private fun checkImageMatrix(matrix: Matrix?) {
        matrix ?: return
        matrix.mapRect(mResultRectF, mRectF)
        if (mResultRectF.width() < mCanShowRect.width()) {
            matrix.postTranslate(mCanShowRect.centerX() - mResultRectF.centerX(), 0F)
        } else {
            if (mResultRectF.right < mCanShowRect.right) {
                matrix.postTranslate(mCanShowRect.right - mResultRectF.right, 0F)
            }
            if (mResultRectF.left > mCanShowRect.left) {
                matrix.postTranslate(mCanShowRect.left - mResultRectF.left, 0F)
            }
        }
        if (mResultRectF.height() < mCanShowRect.height()) {
            matrix.postTranslate(0F, measuredHeight / 2 - mResultRectF.centerY())
        } else {
            if (mResultRectF.bottom < mCanShowRect.bottom) {
                matrix.postTranslate(0F, mCanShowRect.bottom - mResultRectF.bottom)
            }
            if (mResultRectF.top > 0) {
                matrix.postTranslate(0F, mCanShowRect.top - mResultRectF.top)
            }
        }
    }
}