GestureDetector //系统 GestureDetectorCompat //AndroidX 较旧机型有更好的支持
code
package com.example.myapplication.viewimport android.animation.ObjectAnimator import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.util.AttributeSet import android.view.GestureDetector import android.view.MotionEvent import android.view.ScaleGestureDetector import android.view.View import android.widget.OverScroller import androidx.core.view.GestureDetectorCompat import androidx.core.view.ViewCompat import com.example.myapplication.dp import com.example.myapplication.getAvatar import kotlin.math.max import kotlin.math.minprivate val IMAGE_SIZE = 300.dp.toInt() private const val EXTRA_SCALE_FACTOR = 1.5fclass ScalableImageView(context: Context, attrs: AttributeSet) : View(context, attrs) {private val runnable = FlingRunner()private val bitmap = getAvatar(resources, IMAGE_SIZE)private val paint = Paint(Paint.ANTI_ALIAS_FLAG)private var simpleGenListener = SimpleGenListener()private val scaleListener = SimpleScaleListener()private val scaleGenstureDetector =ScaleGestureDetector(context, scaleListener) //ScaleGestureDetectorCompat是扩展private var originalOffsetX = 0fprivate var originalOffsetY = 0fprivate var offsetX = 0f //偏移private var offsetY = 0fprivate var smallScale = 0f //小图private var bigScale = 0f //大图private val scaleAnimation = ObjectAnimator.ofFloat(this, "currentScale", smallScale, bigScale)private val gestureDetector = GestureDetectorCompat(context, simpleGenListener) //gestureDetector.setIsLongpressEnabled(false) //关闭长按private var scroller = OverScroller(context) //滑动flingprivate var big = false //双击开关private var currentScale = 0fset(value) {field = valueinvalidate()}override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {super.onSizeChanged(w, h, oldw, oldh)originalOffsetX = (width - bitmap.width) / 2foriginalOffsetY = (height - bitmap.height) / 2fif (bitmap.width / bitmap.height.toFloat() > width / height.toFloat()) {smallScale = width / bitmap.width.toFloat()bigScale = height / bitmap.height.toFloat() * EXTRA_SCALE_FACTOR} else {smallScale = height / bitmap.height.toFloat()bigScale = width / bitmap.width.toFloat() * EXTRA_SCALE_FACTOR}currentScale = smallScalescaleAnimation.setFloatValues(smallScale, bigScale)}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)val scaleFraction = (currentScale - smallScale) / (bigScale - smallScale)canvas.translate(offsetX * scaleFraction, offsetY * scaleFraction) // val scale = smallScale + (bigScale - smallScale) * scaleFraction//scale 改变后 坐标系也会改变 // canvas.scale(scale, scale, width / 2f, height / 2f)canvas.scale(currentScale, currentScale, width / 2f, height / 2f)canvas.drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint)}private fun fixOffsets() {offsetX = min(offsetX, (bitmap.width * bigScale - width) / 2)offsetX = max(offsetX, -(bitmap.width * bigScale - width) / 2)offsetY = min(offsetY, (bitmap.height * bigScale - height) / 2)offsetY = max(offsetY, -(bitmap.height * bigScale - height) / 2)}override fun onTouchEvent(event: MotionEvent?): Boolean {scaleGenstureDetector.onTouchEvent(event)if (!scaleGenstureDetector.isInProgress){gestureDetector.onTouchEvent(event)}return true}inner class SimpleScaleListener : ScaleGestureDetector.OnScaleGestureListener {override fun onScale(detector: ScaleGestureDetector): Boolean {val tempCurrentScale = currentScale * detector.scaleFactorif (tempCurrentScale < smallScale || tempCurrentScale > bigScale){return false}else{currentScale *= detector.scaleFactor //0 maxreturn true}// currentScale = currentScale.coerceAtLeast(smallScale).coerceAtMost(bigScale) //最大值 // return true //是否统计刚才的值是否拿到}override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {offsetX = (detector.focusX - width / 2f) * (1 - bigScale / smallScale)offsetY = (detector.focusY - height / 2f) * (1 - bigScale / smallScale)fixOffsets()return true}override fun onScaleEnd(detector: ScaleGestureDetector?) {}}// GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener,inner class SimpleGenListener : GestureDetector.SimpleOnGestureListener() {//快速滑动 velocityX 速率 位移 矢量长度override fun onFling(downEvent: MotionEvent?,currentEvent: MotionEvent?,velocityX: Float,velocityY: Float): Boolean {if (big) {scroller.fling(offsetX.toInt(),offsetY.toInt(),velocityX.toInt(),velocityY.toInt(),(-(bitmap.width * bigScale - width) / 2).toInt(),((bitmap.width * bigScale - width) / 2).toInt(),(-(bitmap.height * bigScale - height) / 2).toInt(),((bitmap.height * bigScale - height) / 2).toInt())//下一帧调用 刷新ViewCompat.postOnAnimation(this@ScalableImageView, runnable)}return false}//onMove actionMove distanceX旧位置-新位置override fun onScroll(downEvent: MotionEvent?,cuuentEvent: MotionEvent?,distanceX: Float,distanceY: Float): Boolean {if (big) {offsetX -= distanceXoffsetY -= distanceYfixOffsets()invalidate()}return false}//双击 判断依据 300ms,40ms以下不处理 防手抖override fun onDoubleTap(e: MotionEvent): Boolean {big = !bigif (big) {offsetX = (e.x - width / 2) * (1 - bigScale / smallScale)offsetY = (e.y - height / 2) * (1 - bigScale / smallScale)fixOffsets()scaleAnimation.start()} else {scaleAnimation.reverse()}return true}// //双击之后的后续操作 // override fun onDoubleTapEvent(e: MotionEvent?): Boolean { // return false // }//长按 // override fun onLongPress(e: MotionEvent?) {} // override fun onShowPress(e: MotionEvent?) {}// //不是长按 也不是双击回调 等300ms 没按下 会回调 双击时回调使用准备 时间不够及时 // override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { // return false // }override fun onDown(e: MotionEvent?): Boolean {return true}// //单次点击抬起 判断是否是快速抬起给系统做决定 true false 不影响结果 单击 // override fun onSingleTapUp(e: MotionEvent?): Boolean { // return false // }}inner class FlingRunner : Runnable {override fun run() {//当前位置 速度if (scroller.computeScrollOffset()) {offsetX = scroller.currX.toFloat()offsetY = scroller.currY.toFloat()invalidate()ViewCompat.postOnAnimation(this@ScalableImageView, this)}}} }