文章目录
前言
在view中我们可以重写onTouchEvent
来自定义点击事件,但是MotionEvent
给我们的选择太少,无法满足一些个性化的需求,比如双击,惯性滑动等等,所以我们引入GestureDetectorCompat
监听器来实现一些额外的功能
一、GestureDetectorCompat是什么?
GestureDetectorCompat,翻译系为手势检测器,类似于外挂,钩子,把你在屏幕上的触摸和点击截取到,去替代默认的super.onTouchEvent(event)
,而是走我们在手势检测器中自定义的触摸效果
二、使用步骤
1.定义一个GestureDetectorCompat的实例
代码如下:
private val gestureDetectorCompat = GestureDetectorCompat(context, this)
第二个参数为listener
,让view实现GestureDetector.OnGestureListener
接口,我们就可以直接填入this,但是要去重写抽象方法。
override fun onDown(e: MotionEvent?): Boolean {
TODO("Not yet implemented")
}
override fun onShowPress(e: MotionEvent?) {
TODO("Not yet implemented")
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
TODO("Not yet implemented")
}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
distanceX: Float,
distanceY: Float
): Boolean {
TODO("Not yet implemented")
}
override fun onLongPress(e: MotionEvent?) {
TODO("Not yet implemented")
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
TODO("Not yet implemented")
}
2.重写方法,实现自定义效果
①想让检测器消费一系列的触摸事件,那么就要在重写方法onDown
中去返回true,那么后续的一系列触摸过程才能让手势检测器获取
代码如下:
override fun onDown(e: MotionEvent?): Boolean {
return true
}
②既然我们要去实现双击效果,那么就得再给手势检测器设置一个监听
private val gestureDetectorCompat = GestureDetectorCompat(context, this).apply {
setOnDoubleTapListener(this@ScalableImageView)
}
setOnDoubleTapListener
的参数是传入一个listener,那么我们还是填入view,让view去重写接口GestureDetector.OnDoubleTapListener
的方法即可
重写的方法:
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
TODO("Not yet implemented")
}
override fun onDoubleTap(e: MotionEvent?): Boolean {
TODO("Not yet implemented")
}
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
TODO("Not yet implemented")
}
简化:
追踪GestureDetectorCompat
的构造函数的源码我们可以看到,内部帮我们判断了listener的类型,如果是GestureDetector.OnDoubleTapListene
的实现类,那么就会帮我们去执行setOnDoubleTapListener
方法,不用我们再去配置,所以代码可以简化为:
private val gestureDetectorCompat = GestureDetectorCompat(context, this)
③实现双击变大变小
效果:
④重写onDoubleTap
目的是双击实现图片的放大缩小,所以我们做一个动画,控制图片的大小
private var scaleFraction = 0f
set(value) {
field = value
invalidate()
}
private val animator by lazy {
ObjectAnimator.ofFloat(this, "scaleFraction", 0f, 1f) }
在ondraw
中,拿到要缩放的比例系数,实现从小图片到大图片
val scale = smallScale + (bigScale - smallScale) * scaleFraction
scale(scale, scale, width / 2f, height / 2f)
drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint)
所以在实现双击的方法中,从小到大就是正常播放动画,从大到小就是反向播放动画
override fun onDoubleTap(e: MotionEvent?): Boolean {
if (isBig) {
//本来是大图片,那么就从小到大
animator.reverse()
} else {
animator.start()
}
isBig = !isBig
return true
}
④实现惯性滑动
核心是重写onFling
方法
创建一个OverScroller
对象
private val overScroller = OverScroller(context)
OverScroller
的作用:控制一个点在一定范围内的惯性滑动
如图所示
那么如何控制一张图片的惯性移动呢?
把图片在大框中的移动等价为触摸点在小框中的移动,触摸点在x,y轴上移动的位移,同步到图片在xy轴上的偏移,那么就可以实现图片的惯性
说白了就是把你的触摸点控制在一个小框的范围内,就可以把图片控制在大框内
实现步骤:
①:定义两个变量作为小圆在x轴上移动的偏移,y轴上移动的偏移
private var offsetX = 0f
private var offsetY = 0f
②:重写onFling
方法
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
overScroller.fling(
offsetX.toInt(),
offsetY.toInt(),
velocityX.toInt(),
velocityY.toInt(),
(-(bitmap.width * bigScale - width) / 2f).toInt(),
((bitmap.width * bigScale - width) / 2f).toInt(),
(-(bitmap.height * bigScale - height) / 2f).toInt(),
((bitmap.height * bigScale - height) / 2f).toInt(), 40.dp.toInt(), 40.dp.toInt()
)
postOnAnimation(this)
return false
}
第一个参数和第二个参数:手指点击下去时的位置
第三第四个参数:手指用力滑动时,在两个方向上的速度
第五第六个参数:包围触摸点的小框的范围
最后两个参数指滑出小框边界时,超出又恢复的范围
如下图所示的效果
总结:把触摸点的位移同步给图片
③实现流畅的惯性滑动
让view实现Runnable
接口,重写run
方法,按帧去更新滑动
override fun run() {
//只要惯性还没结束,就递归去更新图片位置
if (overScroller.computeScrollOffset()) {
offsetX = overScroller.currX.toFloat()
offsetY = overScroller.currY.toFloat()
invalidate()
postOnAnimation(this)
}
}
④在onFling
中使用run
方法
postOnAnimation