5.1View事件设计思考
目录介绍
- 01.事件分发概念
- 1.1 事件分发的对象是谁
- 1.2 事件分发的本质
- 1.3 事件在哪些对象间进行传递
- 1.4 事件分发过程涉及方法
- 1.5 Android触摸事件流程总结
- 02.事件分发机制方法
- 2.1 dispatchTouchEvent()
- 2.2 onTouchEvent()
- 2.3 onInterceptTouchEvent()
- 2.4 三个方法执行顺序
- 2.5 三者之间关系
- 03.事件分发背景描述
- 3.1 先看一个案例
- 3.2 该案例事件传递情况
- 04.拖动过程业务举例
- 4.1 先看一个案例
01.事件分发概念
1.1 事件分发的对象是谁
- 事件分发的对象是事件。
- 注意,事件分发是向下传递的,也就是父到子的顺序。
- 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。
- Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
- 主要发生的Touch事件有如下四种:
- MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
- MotionEvent.ACTION_MOVE:滑动View
- MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
- MotionEvent.ACTION_UP:抬起View(与DOWN对应)
- a.1 ACTION_DOWN
- 用户手指的按下操作,一次按下的操作标志者一次触摸事件的开始。
- a.2 ACTION_UP
- 用户手指离开屏幕的操作,一次抬起操作标志者一次触摸事件的结束
- a.3 ACTION_MOVE
- 用户手指按压屏幕后,在松开之前,如果距离超过一定得阈值,那么会被判定为ACTION_MOVE。一般情况下,手指的轻微移动都会触发一系列的移动事件。
- a.4 注意
- 1.在一次屏幕触摸事件中,ACTION_DOWN和ACTION_UP者两个事件是必须的,而ACTION_MOVE视情况而定,如果用户仅是点击了一下屏幕,那么可能只会监测到按下和抬起的动作。
- 2.通过MotionEvent可以获得事件发生的x和y坐标,getX和getY返回的是相当于当前View左上角的X和Y坐标,getRawX和getRawY返回的是相当于手机屏幕左上角的X和Y坐标
- a.5 其他
Touch触摸事件 ♦ 在Android中Touch触摸事件主要包括: 点击(onClick) 长按(onLongClick) 拖拽(onDrag) 滑动(onScroll) ♦ 在Android中Touch操作状态主要包括: 按下(ACTION_DOWN) 移动(ACTION_MOVE) 抬起(ACTION_UP) 取消手势(ACTION_CANCEL) 划出屏幕(ACTION_OUTSIDE)
- 事件列:
- 从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件。即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理
- 任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图[01.1图]:
image
1.2 事件分发的本质
- 将点击事件(MotionEvent)向某个View进行传递并最终得到处理
- 即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
- Android事件分发机制的本质是要解决,点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。
1.3 事件在哪些对象间进行传递
- Activity、ViewGroup、View
- 一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
- Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的
- View是所有UI组件的基类
- 一般Button、ImageView、TextView等控件都是继承父类View
- ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup),
- 其本身也是从View派生的,即ViewGroup是View的子类
- 是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。
- 与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。
1.4 事件分发过程涉及方法
- 事件分发过程由这几个方法协作完成
- dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()[01.2图]
1.5 Android触摸事件流程总结
- Android触摸事件流程总结
- 1.一个事件序列从手指触摸屏幕开始,到触摸结束。同一事件序列是以ACTION_DOWN开始,中间有数量不定的ACTION_MOVE事件,最终以ACTION_UP结束
- 2.事件传递顺序是:Activity——>Window——>View;最后顶级View接收到事件后,就会按照事件分发机制去分发事件
- 3.事件传递过程是由外向内的,即事件总是有父元素分发给子元素
02.事件分发机制方法
- 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成,如下图[01.3图]:
2.1 dispatchTouchEvent()
- 如下所示
属性 介绍 使用对象 Activity、ViewGroup、View 作用 分发点击事件 调用时刻 当点击事件能够传递给当前View时,该方法就会被调用 返回结果 是否消费当前事件,详细情况如下: - 1. 默认情况:根据当前对象的不同而返回方法不同
对象 返回方法 备注 Activity super.dispatchTouchEvent() 即调用父类ViewGroup的dispatchTouchEvent() ViewGroup onIntercepTouchEvent() 即调用自身的onIntercepTouchEvent() View onTouchEvent() 即调用自身的onTouchEvent() - 流程解析
- 返回true
- 消费事件
- 事件不会往下传递
- 后续事件(Move、Up)会继续分发到该View
- 返回false
- 不消费事件
- 事件往下传递
- 将事件回传给父控件的onTouchEvent()处理
- Activity例外:返回false=消费事件
- 后续事件(Move、Up)会继续分发到该View(与onTouchEvent()区别)
- 返回true
2.2 onTouchEvent()
- 如下所示
属性 介绍 使用对象 Activity、ViewGroup、View 作用 处理点击事件 调用时刻 在dispatchTouchEvent()内部调用 返回结果 是否消费(处理)当前事件,详细情况如下: - 流程解析
- 返回true
- 自己处理(消费)该事情
- 事件停止传递
- 该事件序列的后续事件(Move、Up)让其处理;
- 返回false(同默认实现:调用父类onTouchEvent())
- 不处理(消费)该事件
- 事件往上传递给父控件的onTouchEvent()处理
- 当前View不再接受此事件列的其他事件(Move、Up);
- 返回true
- 注意一点
- 在这里要强调View的OnTouchListener。如果View设置了该监听,那么OnTouch()将会回调。
- 如果返回为true那么该View的OnTouchEvent将不会在执行 这是因为设置的OnTouchListener执行时的优先级要比onTouchEvent高。
- 优先级:OnTouchListener > onTouchEvent > onClickListener
2.3 onInterceptTouchEvent()
- 如下所示
属性 介绍 使用对象 ViewGroup(注:Activity、View都没该方法) 作用 拦截事件,即自己处理该事件 调用时刻 在ViewGroup的dispatchTouchEvent()内部调用 返回结果 是否拦截当前事件,详细情况如下: - 流程解析
- true-当前ViewGroup(因为View中没有该方法,而没有child的VIew也不需要有拦截机制)希望该事件不再传递给其child,而是希望自己处理。
- false-当前ViewGroup不准备拦截该事件,事件正常向下分发给其child。
2.4 三个方法执行顺序
- 代码如下所示
@Override public boolean onInterceptTouchEvent(MotionEvent e) { LogUtils.e("yc----------事件拦截----------"); return super.onInterceptTouchEvent(e); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { LogUtils.e("yc----------事件分发----------"); return super.dispatchTouchEvent(ev); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent e) { LogUtils.e("yc----------事件触摸----------"); return super.onTouchEvent(e); }
- 执行结果如下
yc----------事件分发---------- yc----------事件拦截---------- yc----------事件触摸----------
2.5 三者之间关系
- 下面将用一段伪代码来阐述上述三个方法的关系和点击事件传递规则
// 点击事件产生后,会直接调用dispatchTouchEvent分发方法 public boolean dispatchTouchEvent(MotionEvent ev) { //代表是否消耗事件 boolean consume = false; if (onInterceptTouchEvent(ev)) { //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件 //则该点击事件则会交给当前View进行处理 //即调用onTouchEvent ()方法去处理点击事件 consume = onTouchEvent (ev) ; } else { //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件 //则该点击事件则会继续传递给它的子元素 //子元素的dispatchTouchEvent()就会被调用,重复上述过程 //直到点击事件被最终处理为止 consume = child.dispatchTouchEvent (ev) ; } return consume; }
03.事件分发背景描述
3.1 先看一个案例
- 讨论的布局层次如下[01.4图]:
- 最外层:Activiy A,包含两个子View:ViewGroup B、View C
- 中间层:ViewGroup B,包含一个子View:View C
- 最内层:View C
- 触摸情况
- 假设用户首先触摸到屏幕上View C上的某个点(如图中黄色区域),那么Action_DOWN事件就在该点产生,然后用户移动手指并最后离开屏幕。
3.2 该案例事件传递情况
- 一般的事件传递场景有:
- 默认情况
- 处理事件
- 拦截DOWN事件
- 拦截后续事件(MOVE、UP)
3.2.1 默认情况
- 即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值
- 那么调用的是这3个方法的默认实现:调用父类的方法
- 事件传递情况:
- 从Activity A---->ViewGroup B--->View C,从上往下调用dispatchTouchEvent()
- 再由View C--->ViewGroup B --->Activity A,从下往上调用onTouchEvent()
- 注:虽然ViewGroup B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent()
- 注意:这一点与onTouchEvent的行为是不一样的。
3.2.2 处理事件
- 假设View C希望处理这个点击事件,即C被设置成可点击的(Clickable)或者覆写了C的onTouchEvent方法返回true。
- 最常见的:设置Button按钮来响应点击事件
- 事件传递情况:
- DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理这个事件
- 因为C正在处理这个事件,那么DOWN事件将不再往上传递给B和A的onTouchEvent();
- 该事件列的其他事件(Move、Up)也将传递给C的onTouchEvent()
3.2.3 拦截DOWN事件
- 假设ViewGroup B希望处理这个点击事件,即B覆写了onInterceptTouchEvent()返回true、onTouchEvent()返回true。
- 事件传递情况:
- DOWN事件被传递给B的onInterceptTouchEvent()方法,该方法返回true,表示拦截这个事件,即自己处理这个事件(不再往下传递)
- 调用onTouchEvent()处理事件(DOWN事件将不再往上传递给A的onTouchEvent())
- 该事件列的其他事件(Move、Up)将直接传递给B的onTouchEvent()
- 该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
3.2.4 拦截DOWN的后续事件
- 假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。
- DOWN事件传递到C的onTouchEvent方法,返回了true。
- 在后续到来的MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法
- 后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()
- 后续事件将直接传递给B的onTouchEvent()处理
- 后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
- C再也不会收到该事件列产生的后续事件。
- 特别注意:
- 如果ViewGroup A 拦截了一个半路的事件(如MOVE),这个事件将会被系统变成一个CANCEL事件并传递给之前处理该事件的子View;
- 该事件不会再传递给ViewGroup A的onTouchEvent()
- 只有再到来的事件才会传递到ViewGroup A的onTouchEvent()