概述 注:本文基于Android 10源码,为了文章的简洁性,引用源码的地方可能有所删减。
本文主要学习一下与View相关的一些原理与实践,如常用的 View.post 和 ViewTreeObserver 的原理,以及老生常谈的 View 事件分发机制的源码解读,自定义 View 及其滑动冲突的解决方式等。有关Android绘制相关的看Android-View绘制原理 。
View.post View.post 1 2 3 4 5 6 7 8 public boolean post (Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null ) { return attachInfo.mHandler.post(action); } getRunQueue().post(action); return true ; }
由Android-Window机制原理 可知,mAttachInfo的赋值在AT.handleResumeActivity后调用dispatchAttachedToWindow方法,因此在Activity.onCreate时,mAttachInfo为null,因此可以接下来看看getRunQueue方法:
1 2 3 4 5 6 private HandlerActionQueue getRunQueue () { if (mRunQueue == null ) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; }
HandlerActionQueue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class HandlerActionQueue { private HandlerAction[] mActions; private int mCount; public void post (Runnable action) { postDelayed(action, 0 ); } public void postDelayed (Runnable action, long delayMillis) { final HandlerAction handlerAction = new HandlerAction(action, delayMillis); synchronized (this ) { if (mActions == null ) { mActions = new HandlerAction[4 ]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; } } private static class HandlerAction { final Runnable action; final long delay; public HandlerAction (Runnable action, long delay) { this .action = action; this .delay = delay; } public boolean matches (Runnable otherAction) { return otherAction == null && action == null || action != null && action.equals(otherAction); } } }
performTraversals是整个View树绘制流程的发起者,从这个方法开始才会执行View的measure,layout,以及draw动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 void scheduleTraversals () { if (!mTraversalScheduled) { mTraversalScheduled = true ; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null ); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } } final class TraversalRunnable implements Runnable { @Override public void run () { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable();void doTraversal () { if (mTraversalScheduled) { mTraversalScheduled = false ; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); performTraversals(); } } private void performTraversals () { getRunQueue().executeActions(mAttachInfo.mHandler); performMeasure(); performLayout(); performDraw(); } public void executeActions (Handler handler) { synchronized (this ) { final HandlerAction[] actions = mActions; for (int i = 0 , count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); } mActions = null ; mCount = 0 ; } }
在ViewRootImpl.performTraversals()
方法中,执行getRunQueue().executeActions(mAttachInfo.mHandler)
传入了Handler,这个Handler从ViewRootImple的代码中可知是主线程的Handler。
在上述代码中,Choreographer 用来处理 Vsync 信号,当接收到 Vsync 信号后,会通过主线程的 Handler 发送消息,然后执行 performTraversals 中的绘制流程,即 performTraversals 的执行也是以Runnable的形式发送消息至主线程消息队列然后执行,那么在performTraversals()中再将我们通过View.post()添加到主线程的Runnable,就需要等待performTraversals()的代码执行完毕以后才能执行,那么View.post的Runnable代码就只能在执行完View的测量布局绘制以后才能执行,因此通过这种方法我们就可以获取到View的宽和高了。
小结 在onCreate中调用View.post方法时,View会先将Runnable放入到一个数组中,当ViewRootImpl执行View的绘制流程时,performTraversals方法会将这个Runnable放入主线程的消息队列之中,又因为performTraversals方法本身就是通过主线程的消息队列执行的,因此当执行完performTraversals方法也就是完成View的绘制流程后,才会执行View.post传入的Runnable。
有时候View的绘制工作并不是依次能够成功的,因此可能会重新发消息到主线程再执行绘制工作,这会导致View的宽或高以及位置发生变化,这个消息就会排在View.post()发送的消息的后边,因此View.post()获取的宽高可能不正确。
ViewTreeObserver 概述 ViewTreeObserver用来注册监听器,在ViewTree发生变化时收到回调,不能被外部实例化,需要通过android.view.View#getViewTreeObserver()方法来获取。在Android中,所有视图由View和View的子类组成,ViewGroup也是view的子类。ViewGroup和View以树形结构一层一层嵌套组合,就形成了视图树。当ViewTree发生变化时,会调用ViewTreeObserver的相关方法来通知这一改变。可以在ViewTreeObserver中add自己的监听器。
基本用法 1 2 3 4 5 6 7 8 9 ImageView imageView = findViewById(R.id.image_view); imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { public boolean onPreDraw () { int height = imageView.getMeasuredHeight(); int width = imageView.getMeasuredWidth(); imageView.getViewTreeObserver().removeOnPreDrawListener(this ); return true ; } });
ViewTreeObserver支持的监听 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public final class ViewTreeObserver { private boolean mAlive = true ; private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners; private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners; private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; private CopyOnWriteArrayList<OnEnterAnimationCompleteListener> mOnEnterAnimationCompleteListeners; private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners; private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners; private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners; private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners; private ArrayList<OnDrawListener> mOnDrawListeners; }
getViewTreeObserver 1 2 3 4 5 6 7 8 9 10 public ViewTreeObserver getViewTreeObserver () { if (mAttachInfo != null ) { return mAttachInfo.mTreeObserver; } if (mFloatingTreeObserver == null ) { mFloatingTreeObserver = new ViewTreeObserver(mContext); } return mFloatingTreeObserver; }
AttachInfo是View的一个内部类,当一个View附着到父Window中时,它会将View和父Window之间的信息存储在AttachInfo当中。AttachInfo中有一个ViewTreeObserver对象,当mAttachInfo为空时,返回一个名为mFloatingTreeObserver的特殊的ViewTreeObserver。
接下来看看mAttachInfo的赋值流程。从Android-Window机制原理-addView 可知,当调用了addView方法后,会调用到ViewRootImpl.setView方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void setView (View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this ) { if (mView == null ) { mView = view; mAttachInfo.mRootView = view; } requestLayout(); es = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); } }
其中mAttachInfo对象在ViewRootImpl的构造方法中实例化。接着会调用requestLayout方法,这里最终会走到performTraversals方法:
1 2 3 4 5 6 7 8 9 private void performTraversals () { final View host = mView; if (mFirst) { host.dispatchAttachedToWindow(mAttachInfo, 0 ); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true ); } }
这里的host就是之前WindowManager.addView中的View对象,考虑Activity.setContentView方法,在Android-Window机制原理 中已经解析过setContentView的原理,它会创建DecorView对象,这个DecorView继承自FrameLayout,是ViewGroup子类,接着在AT.handleResumeActivity方法中会调用ViewManager.addView方法处理这个DecorView对象。即会调用到ViewGroup.dispatchAttachedToWindow方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 void dispatchAttachedToWindow (AttachInfo info, int visibility) { final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0 ; i < count; i++) { final View child = children[i]; child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility())); } final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); for (int i = 0 ; i < transientCount; ++i) { View view = mTransientViews.get(i); view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility())); } }
这里会逐个调用子View的dispatchAttachedToWindow方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void dispatchAttachedToWindow (AttachInfo info, int visibility) { mAttachInfo = info; if (mFloatingTreeObserver != null ) { info.mTreeObserver.merge(mFloatingTreeObserver); mFloatingTreeObserver = null ; } } void merge (ViewTreeObserver observer) { if (observer.mOnWindowAttachListeners != null ) { if (mOnWindowAttachListeners != null ) { mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners); } else { mOnWindowAttachListeners = observer.mOnWindowAttachListeners; } } observer.kill(); } private void kill () { mAlive = false ; }
另外,当ViewGroup通过addView方法添加View时:
1 2 3 4 5 6 7 8 9 10 11 public void addView (View child, int index, LayoutParams params) { requestLayout(); invalidate(true ); addViewInner(child, index, params, false ); } private void addViewInner (View child, int index, LayoutParams params, boolean preventRequestLayout) { child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK)); }
于是mAttachInfo对象就在ViewTree中传开了。
添加/移除监听 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public void addOnPreDrawListener (OnPreDrawListener listener) { checkIsAlive(); if (mOnPreDrawListeners == null ) { mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>(); } mOnPreDrawListeners.add(listener); } public void removeOnPreDrawListener (OnPreDrawListener victim) { checkIsAlive(); if (mOnPreDrawListeners == null ) { return ; } mOnPreDrawListeners.remove(victim); } private void checkIsAlive () { if (!mAlive) { throw new IllegalStateException("This ViewTreeObserver is not alive, call " + "getViewTreeObserver() again" ); } }
分发回调 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public final boolean dispatchOnPreDraw () { boolean cancelDraw = false ; final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners; if (listeners != null && listeners.size() > 0 ) { CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start(); try { int count = access.size(); for (int i = 0 ; i < count; i++) { cancelDraw |= !(access.get(i).onPreDraw()); } } finally { listeners.end(); } } return cancelDraw; }
其它类型的监听器也是通过dispatchOnXXX方法回调的。通过Window相关原理的解析我们知道View的真正绘制流程中有个方法:ViewRootImpl.performTraversals。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 private void performTraversals () { final View host = mView; if (mFirst) { host.dispatchAttachedToWindow(mAttachInfo, 0 ); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true ); } if (layoutRequested) { final Resources res = mView.getContext().getResources(); if (mFirst) { mAttachInfo.mInTouchMode = !mAddedTouchMode; ensureTouchModeLocally(mAddedTouchMode); } } if (!mStopped || mReportNextDraw) { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } if (didLayout) { performLayout(lp, mWidth, mHeight); } if (triggerGlobalLayoutListener) { mAttachInfo.mRecomputeGlobalAttributes = false ; mAttachInfo.mTreeObserver.dispatchOnGlobalLayout(); } boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; if (!cancelDraw && !newSurface) { performDraw(); } else { if (isViewVisible) { scheduleTraversals(); } } } private boolean ensureTouchModeLocally (boolean inTouchMode) { mAttachInfo.mInTouchMode = inTouchMode; mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode); } private void performDraw () { boolean canUseAsync = draw(fullRedrawNeeded); } private void draw (boolean fullRedrawNeeded) { if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false ; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); } mAttachInfo.mTreeObserver.dispatchOnDraw(); }
事件分发机制 概述 Android的事件分发机制是责任链模式 ,如果自己能处理则拦截事件并处理,如果自己处理不了或不确定能否处理则传给下一个对象。当事件从屏幕上产生后,其分发大致经历了几个过程:屏幕->Activity->PhoneWindow->DecorView->ViewGroup->View
,当这个事件没有对象去处理时,它会往回传递,如果还没有被处理,则丢弃该事件。
整个分发过程涉及到了三个重要的方法:
dispatchTouchEvent(): View 和 ViewGroup 都有,将触摸事件传递给目标 View,如果自己是目标 View,则将该事件分发给自己。
onInterceptTouchEvent(): 只存在于ViewGroup中,主要用于在ViewGroup拦截事件。
onTouchEvent(): 处理事件的具体函数。
Activity.dispatchTouchEvent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public boolean dispatchTouchEvent (MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true ; } return onTouchEvent(ev); } public boolean onTouchEvent (MotionEvent event) { if (mWindow.shouldCloseOnTouch(this , event)) { finish(); return true ; } return false ; }
PhoneWindow事件处理 1 2 3 4 5 @Override public boolean superDispatchTouchEvent (MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
DecorView事件处理 1 2 3 4 public boolean superDispatchTouchEvent (MotionEvent event) { return super .dispatchTouchEvent(event); }
ViewGroup事件处理 dispatchTouchEvent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 @Override public boolean dispatchTouchEvent (MotionEvent ev) { boolean handled = false ; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null ) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 ; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); } else { intercepted = false ; } } else { intercepted = true ; } final boolean canceled = resetCancelNextUpFlag(this ) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 ; TouchTarget newTouchTarget = null ; boolean alreadyDispatchedToNewTouchTarget = false ; if (!canceled && !intercepted) { 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(); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0 ) { 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 (childWithAccessibilityFocus != null ) { if (childWithAccessibilityFocus != child) { continue ; } childWithAccessibilityFocus = null ; i = childrenCount - 1 ; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null )) { ev.setTargetAccessibilityFocus(false ); continue ; } if (dispatchTransformedTouchEvent(ev, false , child, idBitsToAssign)) { newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true ; break ; } ev.setTargetAccessibilityFocus(false ); } if (preorderedList != null ) preorderedList.clear(); } } } if (mFirstTouchTarget == null ) { handled = dispatchTransformedTouchEvent(ev, canceled, null , TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null ; TouchTarget target = mFirstTouchTarget; while (target != null ) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true ; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true ; } if (cancelChild) { if (predecessor == null ) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue ; } } predecessor = target; target = next; } } } return handled; } private TouchTarget addTouchTarget (@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 private boolean dispatchTransformedTouchEvent (MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; 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; } final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; if (newPointerIdBits == 0 ) { return false ; } final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null ) { handled = super .dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } 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()); } handled = child.dispatchTouchEvent(transformedEvent); } transformedEvent.recycle(); return handled; }
onInterceptTouchEvent 1 2 3 4 5 6 7 8 9 public boolean onInterceptTouchEvent (MotionEvent ev) { if (ev.isFromSource(InputDevice.SOURCE_MOUSE) && ev.getAction() == MotionEvent.ACTION_DOWN && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) && isOnScrollbarThumb(ev.getX(), ev.getY())) { return true ; } return false ; }
小结 ViewGroup.dispatchTouchEvent 主要做了这些事情:
判断当前 View 所在 Window 是否被其它Window遮挡,如果被遮挡那么当前ViewGroup不处理该事件;
如果是ACTION_DOWN事件,首先会清除之前的所有状态;
判断当前ViewGroup是否拦截当前事件;
如果不拦截当前事件并且当前事件也不取消,则从后向前遍历ViewGroup中所有的子View,并查找到第一个满足条件的子View处理该事件;
如果存在子View处理该事件则结束查找;如果没有子View处理当前事件,那么当前ViewGroup则会处理该事件;
将ACTION_DOWN后续的事件都分发给同一个子View进行处理。如果当前ViewGroup拦截了后续的ACTION_MOVE、ACTION_UP事件,那么该事件会被取消,并且当前ViewGroup也不会处理该事件。
在dispatchTransformedTouchEvent方法中,不论是ViewGroup处理事件还是子View处理事件,最终都会调用到View中的dispatchTouchEvent方法中,即事件的最终处理都是在View中进行。
View事件处理 dispatchTouchEvent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public boolean dispatchTouchEvent (MotionEvent event) { if (event.isTargetAccessibilityFocus()) { if (!isAccessibilityFocusedViewOrHost()) { return false ; } event.setTargetAccessibilityFocus(false ); } boolean result = false ; final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true ; } 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 (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
onTouchEvent 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public boolean onTouchEvent (MotionEvent event) { switch (action) { case MotionEvent.ACTION_UP: if (mPerformClick == null ) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClickInternal(); } } } private boolean performClickInternal () { notifyAutofillManagerOnClick(); return performClick(); } public boolean performClick () { notifyAutofillManagerOnClick(); 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); notifyEnterOrExitForAutoFillIfNeeded(true ); return result; }
小结 在处理ACTION_UP事件时通过调用performClickInternal方法调用到performClick方法中,然后再调用用户设置的点击事件监听器。因此用户设置的listener中的onTouchEvent方法、View中的onTouchEvent方法以及设置的click事件优先级是从高到低的。如果用户设置的onTouch监听返回true,则后续的两个方法就不会执行了。
总结
ViewGroup处理事件时如果不拦截当前事件并且当前事件也不取消,则从后向前遍历ViewGroup中所有的子View,并查找到第一个满足条件的子View处理该事件;如果存在子View处理该事件则结束查找;如果没有子View处理当前事件,那么当前ViewGroup则会处理该事件;将ACTION_DOWN后续的事件都分发给同一个子View进行处理。如果当前ViewGroup拦截了后续的ACTION_MOVE、ACTION_UP事件,那么该事件会被取消,并且当前ViewGroup也不会处理该事件。
如果View只处理down事件,而不处理接下来的其他事件,那么这些事件不会回传给ViewGroup,而是被忽略掉。因为一旦处理了down事件,那么接下来的该系列所有的事件都会交给这个View,因此,如果不处理down以外的事件,这些事件就会被丢弃。
子 View 可以通过调用 ViewParent.requestDisallowInterceptTouchEvent() 方法来控制 ViewParent 对 onInterceptTouchEvent() 的调用,如果后续某个事件被拦截,子View会接收到ACTION_CANCEL事件,该事件后续事件将由拦截该事件的ViewGroup来消费。如果未拦截,则按照正常分发流程处理 。
滑动冲突 外部拦截法 即父View根据需要对事件进行拦截。逻辑处理放在父View的onInterceptTouchEvent方法中。我们只需要重写父View的onInterceptTouchEvent方法,并根据逻辑需要做相应的拦截即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public boolean onInterceptTouchEvent (MotionEvent event) { boolean intercepted = false ; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false ; break ; } case MotionEvent.ACTION_MOVE: { if () { intercepted = true ; } else { intercepted = false ; } break ; } case MotionEvent.ACTION_UP: { intercepted = false ; break ; } default : break ; } return intercepted; }
根据业务逻辑在ACTION_MOVE方法中进行判断,如果需要父View处理则返回true,否则返回false,事件分发给子View去处理。
ACTION_DOWN 需要返回false,否则根据View事件分发机制,后续 ACTION_MOVE 与 ACTION_UP 事件都将默认交给父View去处理。
理论上 ACTION_UP 也需要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也无法触发。而如果父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理。
内部拦截法 即父View不拦截任何事件,所有事件都传递给子View,子View根据需要决定是自己消费事件还是给父View处理。这需要子View使用requestDisallowInterceptTouchEvent方法才能正常工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public boolean dispatchTouchEvent (MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { parent.requestDisallowInterceptTouchEvent(true ); break ; } case MotionEvent.ACTION_MOVE: { if () { parent.requestDisallowInterceptTouchEvent(false ); } break ; } case MotionEvent.ACTION_UP: { break ; } default : break ; } return super .dispatchTouchEvent(event); } public boolean onInterceptTouchEvent (MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { return false ; } else { return true ; } }
内部拦截法要求父View不能拦截ACTION_DOWN事件,否则所有的事件都不会传递给子View。
滑动策略的逻辑放在子View的dispatchTouchEvent方法的ACTION_MOVE中,如果父容器需要获取点击事件则调用 parent.requestDisallowInterceptTouchEvent(false)方法,让父容器去拦截事件。