敞开生长之旅!这是我参与「日新计划 12 月更文挑战」的第1天,点击查看活动概况
前语
前一篇文章讲了View的触发反应机制,对于一个自定义View而言,手势的处理都是重写onTouchEvent函数,或许经过setOnTouchEventListener办法捕捉手势。可是手势的处理,如滑动、接触、双击等检测对应的检测也并不是那么简单,自己一个个造轮子也过于麻烦,万幸的是google早已经给开发者提供了手势捕捉的类- GestureDetector
。经过这个类咱们能够识别许多的手势,首要是经过他的onTouchEvent(event)办法完成了不同手势的识别。尽管他能识别手势,可是不同的手势要怎么处理,应该是提供给程序员完成的。
GestureDetector
在GestureDetector
中一共有三种首要的回调接口 ,OnGestureListener
、OnDoubleTapListener
、OnContextClickListener
这三个接口的办法如下。
public interface OnGestureListener {
boolean onDown(MotionEvent e);
void onShowPress(MotionEvent e);
boolean onSingleTapUp(MotionEvent e);
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
void onLongPress(MotionEvent e);
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
public interface OnDoubleTapListener {
boolean onSingleTapConfirmed(MotionEvent e);
boolean onDoubleTap(MotionEvent e);
boolean onDoubleTapEvent(MotionEvent e);
}
public interface OnContextClickListener {
boolean onContextClick(MotionEvent e);
}
GestureDetector 运用
GestureDector
负责监听手势,而 OnDoubleTapListener
、OnGestureListener
用于开发者自己去处理对应手势的反应
package com.example.androidtemp.view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
public class TouchView extends View implements GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener{
private static final String TAG = "TouchView";
GestureDetector gestureDetector = null;
public TouchView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
gestureDetector = new GestureDetector(context,this);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.i(TAG, "onSingleTapConfirmed: ");
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.i(TAG, "onDoubleTap: ");
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.i(TAG, "onDoubleTapEvent: ");
return false;
}
@Override
public boolean onDown(MotionEvent e) {
Log.d(TAG, "onDown: ");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
Log.i(TAG, "onShowPress: ");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.i(TAG, "onSingleTapUp: ");
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.i(TAG, "onScroll: ");
return false;
}
@Override
public void onLongPress(MotionEvent e) {
Log.i(TAG, "onLongPress: ");
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.i(TAG, "onFling: ");
return false;
}
}
onDown
办法
onDown
办法是在ACTION_DOWN
事情时被调用的,其的返回值决议了View
是否消费该事情,一般咱们肯定是需求消费该事情的,因此其值为true.
public boolean onDown() {
return true;
}
onShowPress
办法
@Override
public void onShowPress(MotionEvent e) {
//进行控件色彩的改变或其他一些动作
}
onShowPress
是用户按下时的一种回调,首要作用是用于给用户一种按压下的状况,能够在该回调中让控件色彩改变或进行一些动作。需求留意的是,onShowPress 办法不是当即回调的,在手指触碰后,在100ms左右后才会回调。在这100ms内假如手指抬起或滚动,该回调办法不会被触发。在前一篇文章View事情分发机制
中提到过自定义View
默许的super.onTouchEvent
完成中,按压状况也是有一个预按压状况的检测,此处的onShowPress
的回调机制也是同理。
onLongPress
办法
用于检测长按事情的,即手指按下后不抬起,在一段时间后会触发该事情。
@Override
public void onLongPress(MotionEvent e) {
}
onLongPress
回调被触发前 onShowPress
必定会被触发。
需求留意的是 onLongPress
一旦被触发,其他事情都不会被触发了。
不过,onLongPress
事情能够被禁止运用,经过如下代码设置,即不会触发长按事情
gestureDetector.setIsLongpressEnabled(false);
onSingleTapUp
办法
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
onSingleTapUP
的返回值不是太重要,不过一般消费了就仍是返回ture吧。
onSingleTapUp
的意思望文生义,即在 手指抬起时触发,不过他跟一般的onClick
、以及onSingleTapConfirmed
有必定差异
单击事情触发:
GCS: onSingleTapUp
GCS: onClick
GCS: onSingleTapConfirmed
类型 | 触发次数 | 摘要 |
---|---|---|
onSingleTapUp | 1 | 单击抬起 |
onSingleTapConfirmed | 1 | 单击承认 |
onClick | 1 | 单击事情 |
双击事情触发:
onSingleTapUp
onClick
onDoubleTap
onClick
类型 | 触发次数 | 摘要 |
---|---|---|
onSingleTapUp | 1 | 在双击的第一次抬起时触发 |
onSingleTapConfirmed | 0 | 双击产生时不会触发。 |
onClick | 2 | 在双击事情时触发两次。 |
能够看出来这三个事情仍是有所不同的,根据自己实际需求进行运用即可
onScroll
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float
distanceY) {
return true;
}
onScroll
办法是用于监听手指的滑动的,e1是第一次ACTION_DOWN
的事情,e2是当前滚动事情。distanceX、distanceY记载了手指在x、y轴滑动的间隔。
需求留意的时,该滑动间隔记载的是上次滑动回调与这次回调之间的间隔差值。且还有一个有意思的留意事项,该差值是 lastEvent-curEvent 得到的,这与正常的逻辑行为不太共同,不过google就这样干了,所以当咱们在计算滑动偏移量时需求对 distanceX、distancesY进行一个 相减的操作而不是相加。
onFling
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return true;
}
用户手指在屏幕快速滑动后,在抬起时(ACTION_UP
)触发该事情。
Fling 中文直接翻译过来便是一扔、抛、甩,最常见的场景便是在 ListView 或许 RecyclerView 上快速滑动时手指抬起后它还会滚动一段时间才会中止。onFling 便是检测这种手势的。
四个参数的介绍如下
参数 | 简介 |
---|---|
e1 | 手指按下时的 Event。 |
e2 | 手指抬起时的 Event。 |
velocityX | 在 X 轴上的运动速度(像素/秒)。 |
velocityY | 在 Y 轴上的运动速度(像素/秒)。 |
运用 velocityX
、velocityY
参数能够完成一个具有必定初速度的滑动,之后该速度随着滑动衰减,直到中止。
一般onFling
能够结合 OverScroller
完成一个均匀减速的滑动作用。
overScroller
的用法在后方介绍。
onSingleTapConfirmed
和onDoubleTap
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
public boolean onDoubleTap(MotionEvent e) {
return false;
}
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
onSingleTapConfirmed
用于监听单击事情,而onDoubleTap
用于监听双击事情。这两个回调函数是互斥的。
onSingleTapConfigrmed
的调用是推迟的,其在 手指按下300ms后触发。
onSingleTapConfigrmed
适合于在 既检测单击事情也检测双击时间时运用。
可是假如仅仅检测单击事情,onSingleTapUp
更合适,onSingleTapConfigrmed
会让用户明显感觉到推迟。
需求留意的是 onDoubleTap
事情并不是第2次抬起时触发的,而是第2次手接触到屏幕时即(第2次ACTION_DOWN)事情时就会触发该事情,假如要确保在第2次抬起时才触发该事情,就需求运用onDoubleTapEvent
办法了
onDoubleTapEvent
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.i(TAG, "onDoubleTapEvent: event:" + e.getActionMasked());
switch (e.getActionMasked()) {
case MotionEvent.ACTION_UP:
Log.i(TAG, "onDoubleTapEvent: ACTION_UP");
break;
}
return true;
}
双击时,onDoubleTapEvent
将会在onDoubleTap
后触发.
双击触发日志:
TouchView: onDown:
TouchView: onSingleTapUp:
TouchView: onDoubleTap:
TouchView: onDoubleTapEvent: event:0(ACTION_DOWN)
TouchView: onDown:
TouchView: onDoubleTapEvent: event:2(ACTION_MOVE)
TouchView: onDoubleTapEvent: event:2(ACTION_MOVE)
TouchView: onDoubleTapEvent: event:1(ACTION_UP)
TouchView: onDoubleTapEvent: ACTION_UP
需求留意的是不论是双击仍是单击,只要按下长期未动且未抬起,都会触发onLongPress
。
第2次按下后常按再抬起日志
TouchView: onDown:
TouchView: onSingleTapUp:
TouchView: onDoubleTap:
TouchView: onDoubleTapEvent: event:0
TouchView: onDown:
TouchView: onDoubleTapEvent: event:2
TouchView: onDoubleTapEvent: event:2
TouchView: onDoubleTapEvent: event:2
TouchView: onShowPress:
TouchView: onDoubleTapEvent: event:2
TouchView: onDoubleTapEvent: event:2
TouchView: onDoubleTapEvent: event:2
TouchView: onLongPress:
ouchView: onDoubleTapEvent: event:1
TouchView: onDoubleTapEvent: ACTION_UP
OverScroller
在 onFling
办法中,曾说过 运用velocityX
,velocityY
两个参数能够完成 View
的滑动作用.
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return true;
}
示例
此处用一个可迁延滑动的小圆球作为示例.
scroll作用图
Fling作用图
代码如下
package com.example.androidtemp.view
import android.view.View
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.widget.OverScroller
import kotlin.math.max
import kotlin.math.min
private const val TAG = "SmallBallView"
class SmallBallView(context: Context?, attrs:AttributeSet?) :View(context,attrs) ,GestureDetector.OnGestureListener{
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val BALL_DIAMETER_SIZE = 100 //球直径长度
private var originOffsetX = 0f
private var originOffsetY = 0f
private var offsetX = 0f
private var offsetY = 0f
private val gestureDetector = GestureDetector(this.context,this)
private val scroller = OverScroller(this.context)
override fun onTouchEvent(event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event);
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
originOffsetX = (w - BALL_DIAMETER_SIZE)/2f
originOffsetY = (h - BALL_DIAMETER_SIZE)/2f
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 偏移
canvas.translate(offsetX,offsetY)
//中间方位画个圆
canvas.drawArc(originOffsetX,originOffsetY,originOffsetX + BALL_DIAMETER_SIZE.toFloat(),originOffsetY + BALL_DIAMETER_SIZE.toFloat(),0f,360f,false,paint)
}
override fun onDown(e: MotionEvent?): Boolean = true
override fun onShowPress(e: MotionEvent?) {}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
return false
}
override fun onLongPress(e: MotionEvent?) {}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
distanceX: Float,
distanceY: Float
): Boolean {
Log.i(TAG, "onScroll: ")
offsetX -= distanceX
offsetY -= distanceY
//移动不能超过圆的一半
offsetX = min(offsetX,width.toFloat()/2)
offsetX = max(offsetX,-width.toFloat()/2)
//移动不能超过圆的一半
offsetY = min(offsetY,height.toFloat()/2)
offsetY = max(offsetY,-height.toFloat()/2)
invalidate()
return true;
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
//约束滑动不能超过一小圆的一半
scroller.fling(offsetX.toInt(),offsetY.toInt(),velocityX.toInt(),velocityY.toInt(),-width/2,width/2,-height/2,height/2)
postOnAnimation(scrollerRunnable)
return true;
}
private val scrollerRunnable = object :Runnable {
override fun run() {
if (scroller.computeScrollOffset()) {
offsetX = scroller.currX.toFloat()
offsetY = scroller.currY.toFloat()
invalidate()
postOnAnimation(this)
}
}
}
}
OverScroller
办法介绍
-
fling
办法
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY) {
fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
}
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY, int overX, int overY) {
//完成逻辑省掉,有兴趣的能够自己去看代码
}
参数 | 简介 |
---|---|
startX、startY | 开端滑动的X(Y)轴方位 |
velocityX、velocityY | 在 X(Y) 轴上的运动速度(像素/秒)。 |
minX、maxX | 滑动时X轴的两个鸿沟值,滑动时一旦抵达鸿沟值,则马上中止 |
minY、maxY | 滑动时Y轴的两个鸿沟值,滑动时一旦抵达鸿沟值,则马上中止 |
overX、overY | 在滑动时,可超出的滑动值,可超过鸿沟值,不过超过鸿沟值后,又会重新滑动回来 |
-
startScroll
办法
startScroll
的滚动默许以一种粘性液体的作用进行滚动。
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);//DEFAULT_DURATION 250 ms
}
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mScrollerX.startScroll(startX, dx, duration);
mScrollerY.startScroll(startY, dy, duration);
}
参数 | 简介 |
---|---|
startX、startY | 开端滑动的X(Y)轴方位 |
dx、dy | 滚动抵达的目标方位 |
duration | 滚动花费时间(单位ms),假如不指定默许时250ms |