内容简介:1、DecorView是activity的根布局。2、activity的window是PhoneWindow实例。3、DecorView是一个FrameLayout。
1、DecorView是activity的根布局。
2、activity的window是PhoneWindow实例。
3、DecorView是一个FrameLayout。
4、ViewGroup继承自View。没有重载View#onTouchEvent()方法。
5、源码版本为android 9.0。
一图胜千言(PhoneWindow、DecorView和Activity布局的关系)(ps:盗用下辉哥的图):
ViewGroup:
1、ViewGroup#dispatchTouchEvent() 对ViewGroup的事件进行分发。
2、ViewGroup#onInterceptTouchEvent() 对ViewGroup的事件进行拦截。
3、ViewGroup#onTouchEvent() 对ViewGroup的事件进行处理(消费)。
View:
1、View#dispatchTouchEvent() 对View的事件进行分发。
2、View#onTouchEvent() 对View的事件进行处理(消费)。
MotionEvent:
1、MotionEvent.ACTION_DOWN Down事件 用户手指按下时触发。
2、MotionEvent.ACTION_MOVE Move事件 用户手指在屏幕上移动时触发
3、MotionEvent.ACTION_UP Up事件 用户手指抬起,离开屏幕时触发
4、MotionEvent.ACTION_CANCEL Cancel事件
完整的MotionEvent = MotionEvent.ACTION_DOWN + n*MotionEvent.ACTION_MOVE + MotionEvent.ACTION_UP
其他:
1、onTouchEvent()返回true时,表示该View或者ViewGroup消费此事件;返回false时,表示该View或者ViewGroup没有消费事件。
2、可对View或ViewGroup设置View.OnTouchListener进行触摸事件监听。OnTouchListener#onTouch()可以对事件进行监听并消费事件。
测试Demo:
定义一个继承View的MyButton类,重载dispatchTouchEvent()和onTouchEvent(),并添加打印输出。
定义一个继承View的MyView类,重载dispatchTouchEvent()和onTouchEvent(),并添加打印输出。
定义一个继承FrameLayout的MyViewGroup类,重载dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent(),并添加打印输出。(继承LinearLayout不需要重载onLayout()方法摆放子View)
注意:
不可继承自Button,否则会默认消费事件。
#MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) daqiBtn.setOnTouchListener(object :View.OnTouchListener{ override fun onTouch(v: View?, event: MotionEvent?): Boolean { when(event?.action){ MotionEvent.ACTION_DOWN -> { Log.d("daqia","View OnTouchListener ACTION_DOWN") } MotionEvent.ACTION_MOVE -> { Log.d("daqia","View OnTouchListener ACTION_MOVE") } MotionEvent.ACTION_UP -> { Log.d("daqia","View OnTouchListener ACTION_UP") } } return false } }) } } 复制代码
#activity_main.xml <com.daqi.daqitouchevent.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.daqi.daqitouchevent.MyButton android:id="@+id/daqiBtn" android:layout_width="120dp" android:layout_height="50dp" android:layout_marginLeft="10dp" android:layout_marginTop="10dp" android:background="@color/colorAccent"/> </com.daqi.daqitouchevent.MyViewGroup> 复制代码
Activity:
当手指触碰屏幕时,触摸事件最先达到Activity#dispatchTouchEvent()。所以我们先来看看Activity的dispatchTouchEvent()。
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { //空方法 onUserInteraction(); } //其实就是调用PhoneWindow的superDispatchTouchEvent() if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } 复制代码
1、Down事件会执行onUserInteraction()方法。Activity#onUserInteraction()是一个空方法,而且翻阅了FragmentActivity和AppCompatActivity都没有发现重载onUserInteraction()方法。所以默认当初空方法处理。
2、Activity中的window是PhoneWindow实例,所以getWindow().superDispatchTouchEvent(ev)其实是调用PhoneWindow的superDispatchTouchEvent()方法。
跳转到PhoneWindow#superDispatchTouchEvent(),发现调用的是mDecor的superDispatchTouchEvent()方法,而mDecor就是DecorView的实例。
#PhoneWindow.java @Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); } 复制代码
3、跳转到DecorView#superDispatchTouchEvent(),看到调用父类的方法super.dispatchTouchEvent(event)。
DecorView继承自FrameLayout,但FrameLayout没有重载dispatchTouchEvent(event)方法,即super.dispatchTouchEvent(event)调用的是ViewGroup#dispatchTouchEvent(event)
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } 复制代码
所以,当DecorView#dispatchTouchEvent()返回false时,会调用Activity#onTouchEvent()处理(消费)该事件。否则表示布局中已有组件处理(消费)了事件。
View:
View作为最底层的"叶子",并不涉及太多的事件分发且没有拦截机制,所以先从View开始研究事件分发。
View#dispatchTouchEvent
//只显示主要代码,其他代码用//....屏蔽掉 public boolean dispatchTouchEvent(MotionEvent event) { //.... boolean result = false; final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture //1、down事件下来先停止嵌套滑动 stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { //.... //ListenerInfo中定义了一堆监听事件类变量,我们设置的view监听事件,都是存储到这里。 //2、比如OnClickListener、OnLongClickListener等 //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //3、如果没有定义onTouch监听事件,则result为false,并直接执行onTouchEvent(event) //如果定义了onTouch监听事件,当mOnTouchListener.onTouch返回true时,result则为true,导致下面!result为false,onTouchEvent(event)无法被执行。 //(mViewFlags & ENABLED_MASK) == ENABLED 判断组件是否为启动状态 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } //.... //4、 return result; } 复制代码
1、先停止嵌套滑动
2、ListenerInfo中定义了很多View的监听事件实例,可从ListenerInfo中获取OnTouchListener实例,回调onTouch()方法。调用setOnTouchListener其实是将传进来的View.OnTouchListener赋值到ListenerInfo的OnTouchListener变量中。
3、当li.mOnTouchListener != null时,会回调View.OnTouchListener#onTouch(),如果我们在onTouch()方法中返回true,表示消费此事件交由onTouch()处理(消费),不会调用View#onTouchEvent()来处理(消费)事件;反之,在onTouch()中返回false,表示不消费此事件,则事件交由onTouchEvent()处理(消费)。
当没有设置View.OnTouchListener监听时,result默认为false,事件默认交由View#onTouchEvent()处理(消费)。
4、当View#onTouchEvent()或者OnTouchListener#onTouch()返回true时(两者只调用一个),View#dispatchTouchEvent()整体返回true。反之,View#dispatchTouchEvent()整体返回false。
View#onTouchEvent
public boolean onTouchEvent(MotionEvent event) { //1、获取view是否可以点击(点击,长按等) final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; //2、判断当前组件是否处于非启动(禁用)状态,即enabled是否为false if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn‘t respond to them. // 如果为禁用状态且组件设置了点击事件,则会消费该事件,但不作响应。 return clickable; } //android有效触摸的范围是48dp,当组件太小,而又因为布局问题不能设置padding时。 //3、可对父组件设置TouchDelegate,以增大点击面积。 if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //4、当组件可点击时,响应点击事件。 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: //.. break; case MotionEvent.ACTION_DOWN: //.. break; case MotionEvent.ACTION_CANCEL: //.. break; case MotionEvent.ACTION_MOVE: //.. break; } //只要组件是可点击的,必定消费事件 return true; } //5、组件不可点击的,则不会消费事件。 return false } 复制代码
1、依据组件的设置点击监听和长按监听的情况,获取表示组件是否可点击的布尔值clickable。
2、当组件处于禁用(DISABLED)状态,返回clickable。如果组件可点击或长按,则会返回true消费事件,但不做处理。(该View处于非启动(禁用)状态,enabled为false)
3、当组件设置了TouchDelegate时,则会直接消费事件。(子View自身体积太小不利于点击,但又限制于布局无法增大自身大小,可对父组件设置TouchDelegate,以增大点击面积。TouchDelegate不在讨论范围)
4、如果View设置了点击监听或长按监听,则会依据事件进行不同的处理,但最后还是会return true消费事件。(switch语句结束后返回true)
5、否则View没有设置点击监听或长按监听,则return false不消费事件。
注意:
1、onTouchLinstener在View#dispatchTouchEvent()中回调,如果重载dispatchTouchEvent,而不调用父类的dispatchToucEvent,则不会有onTouch()回调。
2、onLongClickListener和onClickListener在View#onTouchEvent()中回调,同理,不调用父类的onTouchEvent(),则不会有onClick()和onLongClick()回调。
ViewGroup:
ViewGroup中涉及到更多事件分发和事件拦截机制,会比较难懂。先看一遍源码,再依据demo中的现象在源码中寻找答案,从而更好的学习和加深对源码的认识。
ViewGroup#dispatchTouchEvent()
#ViewGroup.java @Override public boolean dispatchTouchEvent(MotionEvent ev) { //.... // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { //1、在开始新的触摸手势时丢弃所有先前的状态,重置Touch状态标识 cancelAndClearTouchTargets(ev); resetTouchState(); } //标记是否拦截事件 final boolean intercepted; //2、down事件或已经有处理Touch事件的目标了 //当子控件没有消费事件或者被拦截mFirstTouchTarget为空 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //3、标记是否禁止拦截 //因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept) final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //回调拦截方法 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { //4、 intercepted = true; } //... } 复制代码
1、Down事件是整套MotionEvent事件的开始,先丢弃所有先前的状态,重置Touch状态标识。
2、actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null 事件为down事件时,等式成立,进入if语句。
3、获取一个布尔值disallowIntercept(字面意思:不允许拦截),该布尔值决定着调不调用ViewGroup#onInterceptTouchEvent(),而ViewGroup#onInterceptTouchEvent()正是用来拦截事件的。
disallowIntercept赋值true还是false,由当前ViewGroup中的子View决定。
当子View调用requestDisallowInterceptTouchEvent(boolean)方法请求父类不要拦截时,该disallowIntercept为true,标记是否拦截的布尔值intercepted写死为false,表示不拦截。
当没有子View调用requestDisallowInterceptTouchEvent(boolean)方法时,该disallowIntercept为false,回调ViewGroup#onInterceptTouchEvent()。ViewGroup可以在onInterceptTouchEvent()中对事件进行拦截。
4、当不是Down事件 和 mFirstTouchTarget为null时,intercepted = true。先记住,后面用到。(重点mFirstTouchTarget为null)
#ViewGroup.java @Override public boolean dispatchTouchEvent(MotionEvent ev) { //... //每次分发事件前,将newTouchTarget置空 TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //5、不是取消状态且没拦截的情况下 if (!canceled && !intercepted) { //.... //6、判断(x,y)在该View的可视范围内。true则继续往下执行。 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //.... final View[] children = mChildren; //7、遍历子view for (int i = childrenCount - 1; i >= 0; i--) { //.... //通过dispatchTransformedTouchEvent()将事件分发下去 //dispatchTransformedTouchEvent返回true时,表示子view消费了事件 //当第三参数传child(View实例)时,调用该ViewGroup的子View的dispatchTouchEvent() //当第三参数传null时,调用父类(即View)的dispatchTouchEvent() if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //8、对mFirstTouchTarget进行赋值 newTouchTarget = addTouchTarget(child, idBitsToAssign); //标记已派发事件给新的触摸对象 alreadyDispatchedToNewTouchTarget = true; break; } //... } //9、如果遍历完子View,newTouchTarget没有被赋值,即本次没有消费该事件的子View,但之前有子View的消费事件记录(mFirstTouchTarget 不为空) if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. //该事件没有找到触摸对象,但之前有触摸对象,则将新的触摸对象指向最近的触摸对象 newTouchTarget = mFirstTouchTarget; //从链表表头触摸对象开始遍历,寻找下一个触摸对象,指定找到最后的(第一个)触摸对象为止,将其赋值给newTouchTarget变量 while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } //.... } 复制代码
5、上面对定义的是否拦截的布尔值intercepted,在判断语句 !canceled && !intercepted 用上了。if(!canceled && !intercepted)内会对事件进行事件分发。intercepted为true时,表明事件被该ViewGroup拦截了,不会将事件分发给该ViewGroup的子View。
6、ViewGroup#isTransformedTouchPointInView()用于判断该事件的坐标(x,y)是否在子View可视范围内。符合条件才继续往下执行。
7、遍历自己的子View,通过dispatchTransformedTouchEvent()将事件分发出去。dispatchTransformedTouchEvent返回true时,表示子view消费了事件。(第三个参数传递了当前遍历的子View)
子View的遍历是倒序来得,其实是先选择点击范围内的顶层View,再一层一层将事件分发下去。
#ViewGroup.java private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { //.... // Perform any necessary transformations and dispatch. //child为空 调用super.dispatchTouchEvent(),即调用View#dispatchTouchEvent(),而View#dispatchTouchEvent()中会调用View#onTouchEvent() if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } //调用子View的dispatchTouchEvent(),将事件分发下去。 handled = child.dispatchTouchEvent(transformedEvent); } //.... } 复制代码
当第三参数传 child(View实例) 时,调用该子View的dispatchTouchEvent()。
当第三参数传 null 时,调用父类(即View)的dispatchTouchEvent()。而View#dispatchTouchEvent中,会调用View#onTouchEvent()。即调用自身的onTouchEvent(),因为ViewGroup没有重载onTouchEvent()。
8、dispatchTransformedTouchEvent()的返回值作为if语句的判断条件。当if语句为true时,说明子View消费事件。然后调用addTouchTarget(),将子View封装成TouchTarget,并将其赋值到mFirstTouchTarget。break退出循环,不再分发Move事件。(也就是说只把事件分发给一个消费该事件的子View)
#ViewGroup.java private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; //对mFirstTouchTarget进行赋值 mFirstTouchTarget = target; return target; } 复制代码
TouchTarget是一个链表解构,可以看到将target最终会赋值到mFirstTouchTarget,而上一步是将target的下一个索引指向mFirstTouchTarget,也就是说target的下一个索引指向的是上一个消费事件的target,而本次消费事件的target作为链表的第一个元素。
然后将alreadyDispatchedToNewTouchTarget设为true,标记已分发事件给新的TouchTarget
9、遍历完子View,newTouchTarget仍然没有被赋值,即本次子View没有消费事件。但mFirstTouchTarget 不为空,表示之前有子View的消费事件的记录。则遍历链表,找出第一个消费事件的target,将其赋值给newTouchTarget对象。
#ViewGroup.java @Override public boolean dispatchTouchEvent(MotionEvent ev) { //.... if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view.(没有触摸目标,所以将其视为普通视图。) //10、即ViewGroup调用父类(View)的dispatchTouchEvent() // 如果View的dispatchTouchEvent中返回true,消费了此事件,则handled会为true,ViewGroup#dispatchTouchEvent()返回true。表示告诉该ViewGroup的父视图,该视图消费了事件。 // 否则,无子View消费事件,自身也不消费事件的情况下,则该ViewGroup#dispatchTouchEvent()返回false,表示不消费事件 // 因为模版设计模式,通过View的dispatchTouchEvent进行对ViewGroup的onTouch() 和 onTouchEvent()进行调用。 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; //11、遍历TouchTargt树,分发事件 while (target != null) { final TouchTarget next = target.next; //如果新的触摸目标依据派发过事件 且当前触摸目标等于新的触摸目标。 //alreadyDispatchedToNewTouchTarget在上面子View消费会进行赋值为true if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { //既然子View依据消费了事件,则ViewGroup#dispatchEvent()必定返回true handled = true; } else { //标记是否让child取消处理事件 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //派发事件 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { //ViewGroup#dispatchEvent()返回true handled = true; } //12、cancelChild为true时,将当前target从TouchTarget链表中移除 if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } } 复制代码
10、如果mFirstTouchTaget没有被赋值,则表示本次包括之前的,没有子View消费事件。调用ViewGroup#dispatchTransformedTouchEvent(),第三个参数传递null,调用自身的onTouch()和onTouchEvent()。自己尝试消费事件,如果自身不消费事件,则ViewGroup#dispatchTouchEvent()返回false。
11、如果mFirstTouchTaget不为空,则遍历TouchTaget链表。
alreadyDispatchedToNewTouchTarget为true只有在本次事件被子View消费的情况下不为空。即调用addTouchTaget()方法后设置为true。
但newTouchTarget存在两种情况不为空:
一种是本次事件被子View消费了,调用了addTouchTaget();
一种是本次事件没有被消费,但之前有子View消费事件(可见ViewGroup#dispatchTouchTaget分析第9条)
当本次事件被子View消费时,alreadyDispatchedToNewTouchTarget为true,且newTouchTarget等于mFirstTouchTarget。
alreadyDispatchedToNewTouchTarget && target == newTouchTarget条件成立,因为target初始赋值是mFirstTouchTarget。则直接将ViewGroup#dispatchTouchEvent()返回值设置为true。
alreadyDispatchedToNewTouchTarget && target == newTouchTarget条件不成立时,获取一个标记是否让child取消处理事件的布尔值cancelChild,同时以第二个参数传入ViewGroup#dispatchTransformedTouchEvent()。
cancelChild的值由resetCancelNextUpFlag(target.child) || intercepted 决定。resetCancelNextUpFlag()看不懂,但当ViewGroup拦截事件时,intercepted = true,则cancelChild = true。
当布尔值cancel为true时,也就是cancelChild为true时,会将事件修改为MotionEvent.ACTION_CANCEL,在分发给子View。然后复原MotionEvent。并将target从TouchTarget链表中移除。
当布尔值cancel为false时,也就是cancelChild为false时,会将事件原封不动的分发给子View。走正常分发的流程。
#ViewGroup.java private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) { final boolean handled; //标记当前属于的MotionEvent类型 final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { //将事件改为MotionEvent.ACTION_CANCEL,并分发给子View event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } //复原当前的MotionEvent类型 event.setAction(oldAction); return handled; } //.... } 复制代码
总得来说就是:
遍历TouchTarget链表,当本次事件已经被子View消费时,则将返回值设为true,表示本ViewGroup尝试消费该事件。
判断当前是否发送MotionEvent.ACTION_CANCEL事件,如果intercepted为true导致cancelChild为true时,将事件改成ACTION_CANCEL事件,发送给链表每一个元素,同时将其从TouchTarget链表中删除。否则cancelChild为false时,将事件原封不动的发送给链表中的元素。
当链表中的元素消费事件时,则将ViewGroup#dispatchTouchEvent()的返回值改为true。
示例1、
正常的重载ViewGroup和View关于事件分发的方法,不做任何改动,但对事件进行打印输出。
Down和Up事件传递的顺序是:
ViewGroup#dispatchTouchEvent() -> ViewGroup#onInterceptTouchEvent() -> View#dispatchTouchEvent -> View#onTouchEvent() -> ViewGroup#onTouchEvent()
现象:
手指按下去后,无论怎么移动,然后抬起都是只有这5个打印。从打印看只触发了Down事件,没有Move事件和Up事件。并传递给自己的子View。
解析:
dowm事件时,actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null条件是成立的。所以intercepted依据ViewGroup#onInterceptTouchEvent()返回值来赋值,ViewGroup#onInterceptTouchEvent()默认返回false。
条件!canceled && !intercepted成立时,遍历子View,通过dispatchTransformedTouchEvent(child)对事件进行分发。即调用子View的dispatchTouchEvent()方法和View#onTouchEvent()方法。
当事件没有子View消费时,if(dispatchTransformedTouchEvent(child))条件不成立,导致mFirstTouchTarget依旧为空。
继续往下执行,到if (mFirstTouchTarget == null)时,条件成立。则调用dispatchTransformedTouchEvent(null)。传入的子View为null,调用父类也就是View的dispatchTouchEvent(),对ViewGroup的onTouchEvent()进行调用(ViewGroup没重载onTouchEvent())。
move事件时,由于已经不是down事件了,且mFirstTouchTarget依旧为空。则intercepted被设置为true。if(!canceled && !intercepted)条件不成立,无法进入该if语句中调用dispatchTransformedTouchEvent(child)对move事件进行分发。子View也就不会再接受到后续的事件。
疑问:
不分发给View事件,但ViewGroup#dispatchTouchEvent()依据被调用,为什么没打印。
因为该ViewGroup不是根布局,相对于DecorView,自定义的ViewGroup属于子View。是DecorView不分发事件下来,并且我们也没有消费事件。
示例2、
正常的重载ViewGroup和View关于事件分发的方法,在其中一个View#onTouchEvent()的down事件时返回true,并对事件进行打印输出。
现象:
donw事件中,事件都分发到ViewGroup的全部子View中。当子View2返回true消费事件后,后续事件都只返回给该子View组件。
解析:
按照正常的事件分发流程,mFirstTouchTarget指向的child是子View2。当move事件下来时,intercepted由onInterceptTouchEvent()赋值,默认为false,条件if(!canceled && !intercepted)成立。但后续判断语句不成立。actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE(通过debug发现的,但看不懂)。不符合再次将事件全部分发给子View的情况。
离开if(!canceled && !intercepted)语句。直接遍历TouchTarget链表,将事件分发给链表中的每一个元素。子View2在down事件时消费了事件,固存在与链表中,所以后续的事件都会分发给子View2。
@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (!canceled && !intercepted) { //不满足条件时表示不需要再重新寻找响应事件的View(看不懂) if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { } } } 复制代码
示例3、
正常的重载ViewGroup和View关于事件分发的方法,在其中一个View#onTouchEvent()的down事件时返回true,ViewGroup在第一个move事件时拦截事件,并对事件进行打印输出。
现象:
子View在onTouchEvent中return true,后续事件都会返回到自定义的ViewGroup中,但在第一个move事件时,ViewGroup#onInterceptTouchEvent()返回true将事件拦截了,交由ViewGroup处理事件,后续的事件都交由ViewGroup处理。同时子View接受到CANCEL事件。
解析:
ViewGroup#onInterceptTouchEvent()返回true时,intercepted也为true。if(!canceled && !intercepted)条件不成立,直接遍历TouchTarget链表。
但此时intercepted也为true,cancelChild = resetCancelNextUpFlag(target.child) || intercepted,也就是说cancelChild为true,当cancelChild第二个参数传入dispatchTransformedTouchEvent()时,会给子View发送CANCEL事件,并将该子View从链表中删除。直至到TouchTarget清空。
当TouchTarget被清空后,mFirstTouchTarget为null,后续只调用ViewGroup#onTouchEvent()尝试消费事件。
总结归纳图:
遗留问题:
为什么子View消费了事件,mFirstTouchTarget!= null,在move事件时if(actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)不成立?
按debug看到的结果,直接跳到遍历TouchTarget链表来将后续的事件分发。不再通过分发事件寻找消费事件的子View,直接根据TouchTarget链表将事件分发下去可以理解。但链表什么情况下才会存在更多的元素,并且这段代码是在什么情况下才被使用到?希望大佬们指点下。
if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } 复制代码
demo地址:链接: pan.baidu.com/s/1dRNiQXAt… 提取码: jepe
以上所述就是小编给大家介绍的《Android事件分发源码归纳》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JS 压缩/解压工具
在线压缩/解压 JS 代码
HEX HSV 转换工具
HEX HSV 互换工具