Android View事件传递与源码分析
2017-08-06 17:07
351 查看
1、MotionEvent
当手指点击屏幕后会产生一系列事件: ACTION_DOWN——手指首次触碰屏到幕 ACTION_MOVE——手指在屏幕上滑动 ACTION_UP——手指离开屏幕
一般情况下,手指从按下到离开屏幕会发生一系列事件:
DOWN->MOVE(MOVE事件可能有很多次,也可能没有,取决于有没有滑动和滑动距离)->UP。
Android系统这这些事件封装成MotionEvent类,在这个类里有点击事件类型、点击时间以及点击坐标等信息。
当我们手指首次按下时,会产生一个ACTION_DOWN类型的MotionEvent对象,当手指不断移动时,又会不断产生ACTION_MOVE类型的MotionEvent对象,当手指离开屏幕时,产生一个ACTION_UP类型的MotionEvent对象。这就是我们一次触摸屏幕将会生成的一系列事件,我们称为一个事件序列,以ACTION_DOWN开始,中间有数量不定的ACTION_MOVE,以ACTION_UP结束。
View分发传递的点击事件就是这些系统封装好的MotionEvent对象,并通过获取MotionEvent对象封装的信息对触摸事件进行处理。
2、事件传递
Android的UI结构如下图所示当点击事件生成,首先拿到事件的是Activity,Activity又将事件传递给PhoneWindow,PhoneWindow将事件传递给DecorView。DecorView是顶级View,我们在Activity中使用setContentView()就是将布局放在DecorView下的ContentView,而View对事件的分发也是从DecorView开始的。
1、Activity和PhoneWindow的事件传递
Activity和PhoneWindow的事件传递比较简单,通过看几行源码就明白了Activity#dispatchTouchEvent(MotionEvent ev) public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
PhoneWindow#superDispatchTouchEvent(MotionEvent event) public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
当系统将事件传递给Activity后会调用Activity#dispatchTouchEvent(MotionEvent ev)对事件进行分发,getWindow().superDispatchTouchEvent(ev)将事件传递给PhoneWindow,并调用PhoneWindow的superDispatchTouchEvent(ev)将事件往下传递,这个传递过程层层嵌套,直到对底层的View,
如果没有任何View处理这个事件,则返回false,那么Activity的onTouchEvent()会被调用。也就是说,当最终没人处理某个事件时,Activity会处理该事件。
再看看PhoneWindow#superDispatchTouchEvent(MotionEvent event),其实它并没有做什么,单纯将事件传递非DecorView,PhoneWindow也没有处理事件的能力,即使DecorView包括它的下级View都没有处理事件,PhoneWindow也无法处理该事件,而是直接上传给Activity处理。
2、View事件传递的三个方法
前面我们介绍了Activity和PhoneWindow的事件传递,PhoneWindow将事件传递给DecorView,就开始了最重要也是最常用的事件传递过程,即事件在View的传递过程。点击事件在View的传递由三个很重要的方法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法。
public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前的View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEevent方法的影响,true表示消耗当前事件,false表示不消耗。
public boolean onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent方法中被调用,用来判断当前View是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。此方法只在ViewGroup中存在,View没有此方法,很好理解,作为最低层的View,已经没有下级View可以往下传递了,所以不必判断是否拦截。
public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent中被调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接受到事件。
这三个方法的关系可以用如下伪代码表示
以下代码来自《Android开发艺术探究》 public boolean dispatchTouchEvent(MotionEvent event) { boolean consume = false; if( onInterceptToucnEvent(ev) ){ consume = onTouchEvent(ev) }else{ consume = child.dispatchTouchEvent(ev) } return consume; }
在dispatchTouchEvent中调用onInterceptTouchEvent判断是否拦截事件,若拦截,则调用当前View的onTouchEvent处理事件,onTouchEvent返回的值作为dispatchTouchEvent的返回值,表示是否消耗该事件;若不拦截,则调用子View的dispatchTouchEvent,将事件往下传递,如此递归将点击事件传递下去。
3、View事件传递过程
前面介绍了负责View事件传递的三个重要方法,这里将介绍这三个方法是怎样将一个点击事件层层传递的,这里单纯从业务流程描述,不涉及三个方法的源码分析。先看下面这张图(网上找的)
在上图中,可以把每一个虚线框视为一个dispatchTouchEvent方法。
这张图有些不完整,在确定拦截事件后,会先判断是否有设置onTouchListener,有的话onTouch()方法会优先执行,关于onTouch、onTouchEvent和onClick这三个处理事件的方法在后面会详细介绍,这里先不管。
还有就是,图中onTouchEvent部分可能会有误导,让读者以为是先返回再处理事件,事实上处理事件就在onTouchEvent中实现,然后再返回,且返回值跟是否处理事件无关,只表示是否消耗事件,这里的处理事件指的是在onTouchEvent中进行的逻辑操作。例如我们可以在onTouchEvent中根据拿到的MotionEvent处理一些事务,也可以不处理事务,返回值可以返回false,也可以返回true,但要注意的是若是返回false,那么同一事件序列的其他事件都不会传递给此View,并且交给它的父元素去处理,即调用父元素的onTouchEvent。一般情况下,我们都需要一个完整的事件序列,包括down、move及up事件,因此,若想处理一个完整的事件序列,要返回true,表示消耗此事件。关于这点,后面也会详细解释。
这张图已经基本将View事件传递的过程表现清楚,但是有一些地方还是要说明一下。
我们知道,Android中所有的控件、继承ViewGroup的各种布局都继承自View,图中,顶级父View可视为DecorView,DecorView继承自FrameLayout,父View可视为任何一个layout布局,如LinearLayout等等,子View则是我们常用的控件如Button、TextView
4000
等,内部没有onInterceptTouchEvent方法。图中的三层结构基本上可以表示出绝大多数情况了,就算嵌套再多的布局,也一样,不过是多重复几次而已。
当PhoneWindow调用DecorView的dispatchTouchEvent(MotionEvent ev),便将点击事件MotionEvent传递给了DecorView并启动DecorView的事件传递过程,而DecorView的dispatchTouchEvent又会调用子View的dispatchTouchEvent,就这样递归调用,将事件传递下去。
从图中可以看到,当一个事件一直不被拦截直至传递到最下层的View时,View的dispatchTouchEvent会调用onTouchEvent方法(先假设没有注册onTouchListener),onTouchEvent的返回值就是此View dispatchTouchEvent的返回值,若是返回false表示不消耗此事件,那么此事件就要开始往上传递,上层View会调用自己的onTouchEvent,若是上层View的onTouchEvent也返回false,又继续往上传递,重复刚才步骤。若是都没有消耗此事件,那么此事件最终会上传到Activity。
以上便是View事件传递的过程,此外还有几个结论,也是View事件传递的关键细节。
1、当某个View决定拦截事件,那么这个事件序列内的所有事件都会交给此View处理,并且它的onInterceptTouchEvent不会再被调用。
2、某个View一旦开始处理时间,如果它不消耗ACTION_DOWN事件,即onTouchEvent返回false,那么后续同一事件序列的其他事件都不会交给它处理,并且事件重新交给父元素去处理,即父元素的onTouchEvent会被调用。
这样说有些抽象,举个栗子。一个序列事件中ACTION_DOWN首先被拿到,并开始传递,传递到中间的一个View时,被拦截了,也就停止了往下传递,因为这个序列事件是这个View想要的,这个序列事件找到归宿了,接着这个View的onTouchEvent被调用(还是先不考虑onTouchListener),但是在onTouchEvent中处理完这个ACTION_DOWN事件后若是返回false,那就糟糕了,因为,这个false告诉这个View的父元素:这个不是我想要的,我包括我的下级没人想要这个事件!这表示已经往下遍历一遍所有点击区域的View了,都没人消耗这个事件,就开始往上传递了,调用父元素的onTouchEvent,就是上面流程图往上传递的部分。接着往上传递的过程中,要么有View的onTouchEvent返回true,消耗这个ACTION_DOWN事件,然后这个事件序列后面的事件都将交由消耗事件的这个View处理;要么所有View的onTouchEvent返回false,那么这个ACTION_DOWN事件将传递到Activity中,然后同一事件序列的后续事件,则在传递到DecorView后直接被作为顶级View的DecorView拦截,若DecorView不消耗该事件,那么将传递回Activity。
3、enable属性不影响View对事件的拦截消耗,将一个View设置为disable状态,该拦截消耗的还是会拦截消耗。
4、如果View不消耗ACTION_DOWN以外的其他时间,这个View仍然会收到后续同一个事件序列的事件,但因为这个View不消耗这些事件,所以这些会在这个View中消失,而不是往上传递调用父元素的onTouchEvent。
4、View对点击事件分发过程源码分析
View对点击事件的分发主要是各个继承自ViewGroup的layout来进行的,而子View已经是UI框架的最底层了,不需要也无法往下分发了。View对事件的分发就在dispatchTouchEvent中,我们接下来就分析下ViewGroup的dispatchTouchEvent方法。顶级View(一般都是ViewGroup)拿到点击事件,并且dispatchTouchEvent被调用,开始对事件分发,这这个方法里首先判断是否拦截该事件,下面是方法中关于事件拦截的代码。
ViewGroup#dispatchTouchEvent(MotionEvent ev)部分代码 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //若有该View的子元素的dispatchTouchEvent返回true,则mFirstTouchTarget指向该子元素,若View将事件拦截或没有子元素消耗事件,则mFirstTouchTarget为null //并且当新的序列事件到来时,mFirstTouchTarget会被清空 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; }
从上面的源码,我们发现onInterceptTouchEvent并不会每次都被调用,只有当事件类型为ACTION_DOWN或者mFirstTouchTarget不为空的时候才会调用,也就是说,如果当前View将ACTION_DOWN事件拦截(此时mFirstTouchTarget为null),那么当同一事件序列的ACTION_MOVE、ACTION_UP到来时,由于if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)不成立,将不会调用onInterceptTouchEvent,并且都交给该View处理。这验证了前面的第一条结论。
另外,子View可以通过requestDisallowInterceptTouchEvent方法设置FLAG_DISALLOW_INTERCEPT,使父View放弃拦截除ACTION_DOWN外的事件,之所以不能使父View放弃拦截ACTION_DOWN的原因是,当ACTION_DOWN到来时,父View会重置FLAG_DISALLOW_INTERCEPT
ViewGroup#dispatchTouchEvent(MotionEvent ev)部分代码 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);//清空mFirstTouchTarget resetTouchState();//重置各种标志 }
当View不拦截事件,事件将会下发给它的子View处理,下面这段源码是dispatchTouchEvent中关于寻找并下发事件给子View的部分:
ViewGroup#dispatchTouchEvent(MotionEvent ev)部分代码 for (int i = childrenCount - 1; i >= 0; i--) { 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<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; 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 = 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返回true,说明下级子元素消耗该事件 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(); //对mFirstTarget赋值 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(); } ······ //mFirstTouchTarget==null有两种情况 //1、没有合适的子元素可以接收事件 //2、子元素接收事件,但dispatchTouchEvent返回false if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. //ViewGroup自己处理事件 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
上面这段代码,先遍历ViewGroup的所有子元素,然后判断子元素是否能够接受到点击事件。能否接收点击事件的标准主要由两点来决定:点击事件的坐标是否在子元素的区域内和子元素是否在播放动画,只有这两点同时满足的子元素,事件才会传递给它处理,通过调用dispatchTransformedTouchEvent方法来把事件传递的符合条件的子元素的
ViewGroup#dispatchTransformedTouchEvent部分代码 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); }<
e806
/pre>
上面这段代码是dispatchTransformedTouchEvent的核心代码,若找到符合条件的子元素,则将子元素作为child参数传入,然后调用子元素的dispatchTouchEvent,完成一轮事件分发,若是没有找到符合,或接收事件的子元素返回false,则传入null,此时会调用super.dispatchTouchEvent(event),也就是调用所继承的父类View的dispatchTouchEvent来处理点击事件,也就是说ViewGroup自己处理了。
为什么是调用View的dispatchTouchEvent来处理呢?因为ViewGroup的dispatchTouchEvent中并没有具体处理点击事件的内容,而且在处理事件时还要判断是否有注册onTouchListener、onClickListner等事务,这些都会在View的dispatchTouchEvent处理好,所以直接调用View的dispatchTouchEvent,而且我们知道View没有onInterceptTouchEvent方法,调用View的dispatchTouchEvent会直接开始处理点击事件,而不用进行事件分发,关于View的dispatchTouchEvent会在后面介绍。
如果子元素的dispatchTouchEvent返回true,那么mFirstTouchTarget就会被赋值,指向子元素,同时跳出for循环。
mFirstTouchTarget的赋值在addTouchTarget方法中完成
ViewGroup#addTouchTarget private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { //以接收事件并返回true的子元素为参数创建TouchTarget final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; //将创建的target赋给mFirstTouchTarget mFirstTouchTarget = target; return target; }5、View对点击事件的处理
前面我们说了,无论View还是GroupView,在最终要处理点击事件的时候,都是调用View#dispatchTouchEvent方法来处理,因为在这个方法里有关于onTouchEvent、onTouch、onClick的相关逻辑判断与调用,而且View因为已经没有子元素可以往下分发传递了,当事件传递到它时,直接就进行处理,所以它的dispatchTouchEvent都是关于处理事件的,而没有关于分发传递事件的。
还有一点,在前面我们说如果子元素不处理事件或者说不消耗事件,就交由父元素处理,显然,这里说的交由父元素处理包括了父元素的onTouch、onTouchEvent、onClick,只不过前文为了简洁,忽略onTouch和onClick
源码如下所示View#dispatchTouchEvent 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; //判断是否有注册onTouchListner,有的话,执行onTouch //onTouch的返回值表示是否消耗事件 //下面这个if语句,会从左往右判断,所以当前三个表达式成立时,第四个就会执行 //所以onTouch被执行,且若onTouch返回true,if成立,result为true,表示消耗事件 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //若有执行onTouch,且返回值为false,也就是说onTouch不消耗事件 //执行OnTouchEvent,若onTouch返回true,事件被消耗,则onTouchEvent不能被执行 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; }
从上面的代码及注释,我们发现,onTouch的优先级比onTouchEvent高,若onTouch返回True,消耗事件,那么onTouchEvent就不能被执行。
View关于onClick的判断部分在onTouchEvent中,我们直接看代码public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); //内部调用onClick方法 } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; ······ } //只要View的CLICKABLE和LONG_CLICKABLE为true,那么这个View就会消耗事件 return true; } return false; }
我们从上面的代码中发现,View的onTouchEvent方法,几乎都是关于CLICK的判断。所以在自定义控件时,继承View后,得重写onTouchEvent,在这方法中根据需求,实现对事件的处理,并且,若想要控件响应click,则要在onTouchEvent 返回的时候调用View的onTouchEvent :return super.onTouchEvent(event)。
而且,当View的CLICKABLE和LONG_CLICKABLE属性,只要有一个是ENABLE(android:clickable = “true” 或android:longClickable=”true”),即使这个View是DISENABLE状态(android:enabled=”false”),那么不管是否有注册onClickListener,View都会消耗事件。
onClick的具体判断调用封装在performClick方法中public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
关于click属性,当注册监听器后,setOnClickListener和setOnLongClickListener会分别把View的click属性和long_click属性设置为true,所以想要将click或long_click设置为false,要在注册监听器之后设置。
下面是注册监听器代码public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; } public void setOnLongClickListener(@Nullable OnLongClickListener l) { if (!isLongClickable()) { setLongClickable(true); } getListenerInfo().mOnLongClickListener = l; }
相关文章推荐
- Android View事件传递机制-源码分析
- Android 源码分析AccessibilityService拦截VR眼镜Key事件以及key事件在View体系的传递
- Android触摸屏ViewGroup事件派发机制详解与源码分析
- Android中ViewGroup、View事件分发机制源码分析总结(雷惊风)
- Android—— View事件分发机制的源码分析
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
- Android事件分发机制源码分析之View篇
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)dispatchtouchevent,ontouch,ontouchevent,onclick
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android View 触摸屏事件派发机制和源码分析
- Android应用开发原理之从ViewGroup源码分析ViewGroup的事件分发机制
- Android View和ViewGroup事件分发机制源码分析
- Android事件传递机制 源码分析
- Android源码分析(二):View的事件分发机制探析
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android 事件分发机制(最新源码6.0分析)--ViewGrop
- Android触摸事件派发机制详解与源码分析二(ViewGroup篇)
- Android事件分发机制源码分析下----ViewGroup事件分发分析
- Android触摸屏事件派发机制详解与源码分析一(View篇)