【Android】Android事件分发机制的学习笔记和理解
前言:
Android事件分发机制让我头疼了很久,很多概念容易混淆,在简书上看到一篇绝赞的文章,在阅读view源码的帮助下,终于可以说是大致搞懂了整个的流程,以下做一些笔记和自己的简略概括总结
这里附上原文地址,再次感谢作者 一文读懂Android View事件分发机制
首先确定几个面试官可能会问到的问题
- 事件分发机制的分发过程
- onTouchEvent,onTouch方法的区别
- 如何去理解dispatchTouchEvent 和 onInterceptTouchEvent,onTouchEvent
- 如何使用requestDisallowInterceptTouchEvent方法,如何调用这个方法可以解决子view和父view之间的冲突
- 各个方法的返回值都代表了什么,怎么去理解
一 事件分发机制的分发过程
引用原文一张流程图,秒懂:
简而言之,就是Activity的dispatchTouchEvent()–>Window–>DecorView(顶级的ViewGrop)的dispatchTouchEvent()点击事件传到了 ViewGrop里,之后再由viewgrop传入他自己的子view里
ViewGrop:可以有很多子view ,所以有dispatchTouchEvent, onInterceptTouchEvent,onTouchEvent(除了分发和触摸事件,还有拦截事件)
View:只有dispatchTouchEvent ,onTouchEvent(只有分发和触摸事件)
参照源码可以更加方便的便于理解:
我们模拟一个情景,来方便理解,引用https://blog.csdn.net/qq_30379689/article/details/53967177这篇文章中的情形
<?xml version="1.0" encoding="utf-8"?> --------------------- 作者:Hensen_ 来源:CSDN 原文:https://blog.csdn.net/qq_30379689/article/details/53967177 版权声明:本文为博主原创文章,转载请附上博文链接!
文章中只是说出了假如我们重写各个方法会出现的情况,并没有说明具体流程,下面我来根据view的源码,分析一下自己的思考过程。
直接分析viewgrop的源码,我把他大致简化一下
public boolean dispatchTouchEvent(MotionEvent ev) { //省略 boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 这里通过requestDisallowInterceptTouchEvent 方法可以设置 FLAG_SPLIT_MOTION_EVENTS 这个标签,来控制是否拦截事件 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 { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // If the event is targeting accessiiblity focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping // state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; //这里是遍历所有的子view for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //在这个方法里会设置newTouchTarget,使得newTouchTarget 不为null newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); //事件在这个dispatchTransformedTouchEvent方法里进行分发 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(); //给mFirstTouchTarget赋值 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } 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; } } } // Dispatch to touch targets. //如果mFirstTouchTarget 不为空,即 这个viewgrop有子view, if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. // 事件在这个dispatchTransformedTouchEvent方法里进行分发,表示事件还未消耗 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //省略 } //省略 return handled; }
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } //省略 }
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
View 的dispatchTouchEvent方法,在ViewGrop类里的dispatchTransformedTouchEvent方法里进行调用,调用方式:super.dispatchTouchEvent(event);
public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
1、dispatchTouchEvent(分发事件)
如果在MyViewGroup01的dispatchTouchEvent方法中返回true,表示需要在MyViewGroup01消费了整个事件,即不会再分发,也不会再处理。dispatchTouchEvent方法中返回true的打印信息
//分发过程
MyViewGroup02 dispatchTouchEvent
MyViewGroup02 onInterceptTouchEvent
MyViewGroup01 dispatchTouchEvent
如果在MyViewGroup01的dispatchTouchEvent方法中返回false,表示在MyViewGroup01点击事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。dispatchTouchEvent方法中返回false的打印信息
//分发过程
MyViewGroup02 dispatchTouchEvent
MyViewGroup02 onInterceptTouchEvent
MyViewGroup01 dispatchTouchEvent
//处理过程
MyViewGroup02 onTouchEvent
作者:Hensen_
来源:CSDN
原文:https://blog.csdn.net/qq_30379689/article/details/53967177
版权声明:本文为博主原创文章,转载请附上博文链接!
分析:当MyViewGroup01的dispatchTouchEvent为true的时候,MyViewGroup02的dispatchTouchEvent 和onInterceptTouchEvent按照顺序执行,MyViewGroup01 的dispatchTouchEvent也能执行,但是MyViewGroup01的 dispatchTouchEvent直接返回了true,所以在第一次调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法的时候(代码115行),就直接返回了true,所以接着又会执行newTouchTarget = addTouchTarget(child, idBitsToAssign)(代码132行)这个方法,这个方法则是给mFirstTouchTarget赋了值,所以当mFirstTouchTarget == null时候才会执行的handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)(代码158行)方法不会被执行,而最关键的view的onTouchEvent方法,就在chlid为null的时候会执行,所以当dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)(代码161行)不执行的时候,就不会调用onTouchEvent方法,所以当MyViewGroup01的dispatchTouchEvent为true的时候,onTouchEvent不会执行,反之,则会执行
更多相关文章
- Android Studio3.0开发JNI流程------在Android原程序添加自己类
- 利用Android的Matrix类实现J2ME的drawRegion的镜像方法
- 调用Android自带日历功能(日历列表单、添加一个日历事件)
- Systrace 分析性能工具使用方法详解
- 简单的Android ROM制作方法:创建刷机包 备份ROM
- Android之A面试题④应用程序内部启动Activity过程(startActivity)
- Android 事件处理(―)(附源码)