一、安卓UI层级
image.pngActivity的结构为:Activity -> PhoneWindow -> DecorView -> ContentView -> ... -> View
二、安卓UI事件分发流程
1.安卓UI的View是树形结构的,基于这样的结构,事件可以进行有序的分发。
事件收集之后最先传递给 Activity, 然后依次向下传递。根据Activity的内部结构也可以看出,事件分发的流程:Activity -> PhoneWindow -> DecorView -> ContentView -> ... -> View。如果View没有消费事件,则会回传。流程如下:View-> ... ->ContentView -> DecorView -> PhoneWindow ->Activity。
总体流程来说,整个流程是遵循责任链模式。上层View既可以直接拦截该事件,自己处理,也可以先询问(分发给)子View,如果子View需要就交给子View处理,如果子View不需要还能继续交给上层View处理。既保证了事件的有序性,又非常的灵活。
2.事件分发、拦截与消费
image.png这三个方法,通过返回 true 和 false 来控制事件传递的流程.
三个方法:
dispatchTouchEvent(事件分发)
onInterceptTouchEvent(事件拦截)
onTouchEvent(事件回传:从View开始,逐级回传)
三、实战
1.创建安卓工程,布局如下
<?xml version="1.0" encoding="utf-8"?>
<com.example.customer.view.View.RootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#4E5268"
android:layout_margin="20dp"
tools:context="com.example.customer.view.MainActivity">
<com.example.customer.view.View.ViewGroupA
android:background="#95C3FA"
android:layout_width="200dp"
android:layout_height="200dp">
<com.example.customer.view.View.View1
android:background="#BDDA66"
android:layout_width="130dp"
android:layout_height="130dp"/>
</com.example.customer.view.View.ViewGroupA>
<com.example.customer.view.View.View2
android:layout_alignParentRight="true"
android:background="#BDDA66"
android:layout_width="80dp"
android:layout_height="80dp"/>
</com.example.customer.view.View.RootView>
2.布局结构图如下:
image.png3.触发事件分析:
按下view1,不放开
事件分发流程如下:
MainActivity: dispatchTouchEvent ->
RootView: dispatchTouchEvent ->
RootView: onInterceptTouchEvent ->
ViewGroupA: dispatchTouchEvent->
ViewGroupA: onInterceptTouchEvent->
View1: dispatchTouchEvent->
View1: onTouchEvent->
ViewGroupA: onTouchEvent->
RootView: onTouchEvent->
MainActivity: onTouchEvent->
image.png
按下view1后,放开
image.png事件分发流程如下:
MainActivity: dispatchTouchEvent ->
RootView: dispatchTouchEvent ->
RootView: onInterceptTouchEvent ->
ViewGroupA: dispatchTouchEvent->
ViewGroupA: onInterceptTouchEvent->
View1: dispatchTouchEvent->
View1: onTouchEvent->
ViewGroupA: onTouchEvent->
RootView: onTouchEvent->
MainActivity: onTouchEvent->
MainActivity: dispatchTouchEvent (Action:ACTION_UP)->
MainActivity: onTouchEvent (Action:ACTION_UP)
注意:MotionEvent. ACTION_UP事件在MotionEvent.ACTION_DOWN结束后触发。
在RootView增加事件(MotionEvent.ACTION_DOWN)拦截,按下view1后,放开
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG,"onInterceptTouchEvent Action:"+MotionEvent.actionToString(ev.getAction()));
if (ev.getAction() == MotionEvent.ACTION_DOWN){
return true;
}else {
return super.onInterceptTouchEvent(ev);
}
}
image.png
从打印结果来看,(MotionEvent.ACTION_DOWN)事件不再往下传,在RootView接收到后回传给MainActivity。
注意:onInterceptTouchEvent() 返回true表示拦截、返回false表示不拦截,不拦截时,跟正常的流程一样。
四、View 事件相关的各个方法调用顺序
onTouchListener > onTouchEvent > onLongClickListener > onClickListener
1、onTouchEvent返回true时,代表消费事件,则事件不回传。
image.png2、onTouchEvent返回false时,代表没有消费事件,则事件回传。
image.png五、核心要点
事件分发原理: 责任链模式,事件层层传递,直到被消费。
View 的 dispatchTouchEvent 主要用于调度自身的监听器和 onTouchEvent。
View的事件的调度顺序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。
不论 View 自身是否注册点击事件,只要 View 是可点击的就会消费事件。
事件是否被消费由返回值决定,true 表示消费,false 表示不消费,与是否使用了事件无关。
ViewGroup 中可能有多个 ChildView 时,将事件分配给包含点击位置的 ChildView。
ViewGroup 和 ChildView 同时注册了事件监听器(onClick等),由 ChildView 消费。
一次触摸流程中产生事件应被同一 View 消费,全部接收或者全部拒绝。
只要接受 ACTION_DOWN 就意味着接受所有的事件,拒绝 ACTION_DOWN 则不会收到后续内容。
如果当前正在处理的事件被上层 View 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。
网友评论