View事件体系(一)

View事件体系(一)

一、View监听

在Android中,View处于十分重要的位置,View的事件体系是每个Android工程师都得掌握的技能。接下来两篇文章,我首先介绍View的相关监听方法,以及部分源码的解析。

1.MotionEvent

在手指接触屏幕之后产生的一系列事件

  • ACTION_DOWN:手指接触屏幕
  • ACTION_MOVE:手指在屏幕上滑动
  • ACTION_UP:手指从屏幕上松开
当用户滑动类似于ListView等控件的时候

ACTION_DOWN -> ACTION_MOVEACTION_MOVE -> ACTION_UP


2.TouchSlop

系统能识别的滑动最小的距离

如何获得
1
ViewConfiguration.get(getContext()).getScaledTouchSlop();
用途
1
当手指在屏幕上的移动距离小于这个值时,则可以判定此次不是滑动。
位置
1
在源码中 frameworks/base/core/res/res/values/config.xml里可以找到


3.VelocityTracker

追踪滑动速度

使用方法:在onTouchEvent方法中
1
2
3
4
5
6
7
8
9
10
VelocityTracker valocityTracker = VelocityTracker.obtain();
valocityTracker.addMovement(event);//添加MotionEvent事件

valocityTracker.computeCurrentVelocity(1000);//计算的时间间隔
int xVelocity = (int) valocityTracker.getXVelocity();//x方向速度
int yVelocity = (int) valocityTracker.getYVelocity();//y方向速度

//当你不用时
valocityTracker.clear();
valocityTracker.recycle();
计算公式
1
V(x) = (X1 - X2)/t

这是在x方向上速度计算公式,其中(x1 - x2)是手指在x轴上滑动的像素差,t是valocityTracker.computeCurrentVelocity(1000)里面设置的毫秒数

y方向上同理。


4.GestureDetector

手势检测,用于辅助检测用户单击、双击、滑动、长按等事件。主要用于复杂手势处理。

使用方法
1.实现OnGestureListener接口

OnDoubleTapListener多一个onDoubleTap的回调,建议在简单场景中使用onTouchEvent,复杂场景才使用GestureDetector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private class MyOnGestureListener implements GestureDetector.OnGestureListener{  

// 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
public boolean onDown(MotionEvent e) {
//...
return false;
}

/*
* 用户轻触触摸屏,尚未松开或拖动
* 由一个1个MotionEvent ACTION_DOWN触发
* 注意和onDown()的区别是该事件会持续一段时间
* 执行顺序为 先onDown()->后onShowPress
*/
public void onShowPress(MotionEvent e) {
//...
}

/* 点击事件,用户轻轻触摸一次屏幕就放开
* 伴随着MotionEvent.ACTION_UP
* 若在轻点中途进行了其他操作,例如滑动,则不会触发该回调
*/
public boolean onSingleTapUp(MotionEvent e) {
//...
return true;
}

/*
* 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
* e1 手指按下的事件
* e2 手指松开的事件
* distanceX x方向滑动的距离
* distanceY y方向滑动的距离
*/
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
//...
return true;
}

// 手指长按触摸屏,由多个MotionEvent ACTION_DOWN触发
public void onLongPress(MotionEvent e) {
//...
}

/* 手指按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发
* e1 手指按下的事件
* e2 手指松开的事件
* velocityX x方向滑动的速度
* velocityY y方向滑动的速度
*/
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
//...
return true;
}
};
2.初始化
1
GestureDetector mGestureDetector = new GestureDetector(this,new MyOnGestureListener());
3.拦截点击事件
1
2
3
4
5
6
7
/*  
* 在onTouch()方法中,调用GestureDetector的onTouchEvent()方法,将点击事件交由GestureDetector
* 来分析是否有合适的回调函数来处理用户的手势
*/
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}

二、事件(MotionEvent)分发

分发流程

1.核心方法
public boolean dispatchTouchEvent (MotionEvent ev)
1
2
用来进行事件的分发,如果事件能够传递到当前View,则该方法一定会被调用。
返回结果受到当前View的 onTouchEvent 和下级View的 dispatchTouchEvent 方法的影响,返回结果表示是否消耗当前事件。
public boolean onInterceptTouchEvent (MotionEvent ev)
1
2
判断是否拦截某个事件,如果当前 View 拦截了该事件,则同一个事件序列当中,
此方法不会被调用,同一事件序列的事件全都交由该 View 处理,返回结果表示是否拦截该事件。
public boolean onTouchEvent(MotionEvent ev)
1
2
在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件。
若不消耗,则以后在同一事件序列的事件,都不会交由该View处理。
2.主要流程的伪代码
1
2
3
4
5
6
7
8
9
10
11
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;//是否消耗事件

if(onInterceptTouchEvent(ev)){//如果拦截
consume = onTouchEvent(ev);//交给该View的onTouchEvent处理
}else{//不拦截
consume = child.dispatchTouchEvent(ev);//分发给子View的dispatchTouchEvent
}

returen consume;
}
3.流程图

image

  1. 当一个点击事件产生之后,事件首先会被传递到 Activity ,然后分发给Window,然后在传递到顶级的根ViewGroup
  2. 在根ViewGroup的dispatchTouchEvent里,这个方法其中一处调用了onInterceptTouchEvent方法,若调用的这个方法的返回值为true就表示他要拦截该事件,接着这个View的onTouchEvent方法。若不拦截当前事件,即返回false,就会传递给子ViewGroup的dispatchTouchEvent方法,直到事件被处理。
  3. 该View的onTouchEvent返回false,则会交由父View的TouchEvent处理。
View的onTouchEvent相关

image

事件处理的优先级

image

当一个View需要处理事件的时候,如果设置了OnTouchListener,那么这个OnTouchListener.onTouch方法会被回调。

若onTouch的返回值为false,则当前View的onTouchEvent方法会被调用

若onTouch的返回值为trueonTouchEvent方法不会被调用。所以优先级onTouch > onTouchEvent



onClickListener是在onTouchEvent方法中设置,所有优先级onTouchEvent > onClickListener


综上:onTouchListener > onTouchEvent > onClickListener

0%