Android----View事件分发机制(二)

    xiaoxiao2021-04-12  40

    上一章主要说了View分发的简单流程,下面通过源码来看清楚分发机制

    科普:当一个点击事件发生后,最开始的传递过程是这样的:Activity–Window–顶级View,当顶级View收到事件后,就会按分发机制把事件分发下去

    当事件传递到顶级View(我们在Activity通过setContentView那个,是个ViewGroup来的),调用dispatchTouchEvent方法(拦截部分和分发事件部分)

    拦截部分:

    // Check for interception. 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); // 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; }

    可以看到有个拦截属性intercepted,有两种情况要判断(有一个为true就调用onInterceptTouchEvent询问自己是否要拦截)

    actionMasked == MotionEvent.ACTION_DOWN:当前点击事件为按下时,ViewGroup总是会问自己是否要拦截事件(调用onInterceptTouchEvent方法,默认不拦截)mFirstTouchTarget != null:如果不拦截Down事件,在分发过程中,若有子View成功消费Down事件,则mFirstTouchTarget 指向那个子View

    结论:若上面两个条件都为false,即在一个事件序列中ViewGroup拦截Down事件或者没有子View想要处理Down事件,则后面的Move,Up事件来到ViewGroup都会直接跳过onInterceptTouchEvent方法(既然子View都不要down事件了,更何况Move,Up事件),把intercepted 赋值为true

    特殊情况:子View可以通过requestDisallowInterceptTouchEvent(boolean)方法来设置父ViewGroup无法拦截出了down以外的其它事件(设置父类标志位FLAG_DISALLOW_INTERCEPT实现),因为ViewGroup在Down事件来到时会对FLAG_DISALLOW_INTERCEPT进行重置,所以父类肯定能收到Down事件

    事件分发部分(前提是ViewGroup不拦截):

    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 (!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); 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(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } }

    遍历ViewGroup可以接受点击事件的子View(子View是否在播放动画和点击事件是否落在子View区域上),调用它们的dispatchTouchEvent,若有一个子View的dispatchTouchEvent返回true,则ViewGroup的mFirstTouchTarget 指向该View,跳出遍历,后面的点击事件都交给该子View处理

    当事件传递给View时,调用dispatchTouchEvent方法

    public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; ...... 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; } } ...... return result; }
    View的dispatchTouchEvent比较简单,首先会检查是否设置了OnTouchListener
    有的话就去调用onTouch方法,如果onTouch返回true,则给result赋值true,导致它的onTouchEvent方法执行不了,所以onTouch方法优先级比onTouchEvent方法高,如果onTouch返回false,则继续执行onTouchEvent没有的话就去执行onTouchEvent方法 下面是View的部分onTouchEvent方法: ...... 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) { ...... 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(); } } } ...... break; case MotionEvent.ACTION_DOWN: ...... break; case MotionEvent.ACTION_CANCEL: ...... break; case MotionEvent.ACTION_MOVE: ...... break; } return true; } ......
    解析:主要有两点
    最上面的判断语句中如果View的CLICKABLE或LONG_CLICKABLE中有一个为true的话,则onTouchEvent方法就会返回true,消费事件,注意所有view的LONG_CLICKABLE默认为false,CLICKABLE的话看具体的控件,button默认为true,textview默认为false在UP事件中,会调用performClick(),如果view设置了OnClickListener,那么在performClick()里面会调用onClick方法

    perfirnClick()方法如下:

    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; }

    到此View的事件分发机制就分析结束了,在这里总结一下:

    当点击事件发生传递到顶级View时(根ViewGroup),调用它的dispatchTouchEvent方法,然后在调用拦截方法询问是否拦截 拦截,调用自己onTouchEvent,后面的同一序列事件来到时,不在调用拦截方法,直接调用自己onTouchEvent方法不拦截,遍历子View看谁需要处理事件,调用子View的dispatchTouchEvent方法(看2)调用View的dispatchTouchEvent方法时 如果设置onTouch方法,则先调用onTouch,若返回true,事件消费,onTouchEvent无法被执行;若返回false,onTouchEvent执行如果没有设置onTouch方法,则调用onTouchEvent,返回true则消费,后面其它同一序列事件都给这个View处理,若返回false则让父ViewGroup继续遍历其它子View调用onTouchEvent方法时: 如果该View的CLICKABLE或LONG_CLICKABLE中有一个为true的话,则返回true消费事件如果有设置OnClickListener的话,会在performClick()中调用该View的onClick()

    这两篇关于View的事件分发机制部分出自书籍《Android开发艺术探索》,目前我在看本书看的不能自拔,这里也建议大家感兴趣的话可以去看一看。最后谢谢大家观看,希望大家看到这篇文章都能有收获

    转载请注明原文地址: https://ju.6miu.com/read-667550.html

    最新回复(0)