图片预览组件
支持缩放、平移手势、双击缩放
知识点:
- 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
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()
private var mResultRectF = RectF()
private var mCanShowRect = Rect()
private val mGestureMatrix: Matrix = Matrix()
init {
scaleType = ScaleType.MATRIX
mCustomPaddingLeft = paddingLeft
mCustomPaddingRight = paddingRight
mCustomPaddingTop = paddingTop
mCustomPaddingBottom = paddingBottom
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() {
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)
}
}
}
}