View事件传递机制

  • 事件传递每次都要递归查找处理事件的View吗?
  • ACTION_CANCEL存在的意义?
  • 父View再那个事件拦截子View?
  • 子View再那个时机告诉父View不要对自己的事件进行拦截呢?
// ViewGroup父容器
public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        Log.e(Constant.TAG, this.getClass().getName() + " onTouchEvent + " + event.getAction());
        // 需要注意的是父容器不能拦截DOWN事件,如果拦截了就没子View啥事了
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            return super.onInterceptHoverEvent(event);
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            // 父容器拦截MOVE事件的时候改父容器的mFirstTarget = TouchTarget(child = MyButton)
            reflectTouchTargetInstance(this, "onInterceptTouchEvent");
            return true;
        } else {
            return true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
//        reflectTouchTargetInstance(this, "onTouchEvent");
        Log.e(Constant.TAG, this.getClass().getName() + " onTouchEvent + " + event.getAction());
        return super.onTouchEvent(event);
    }

    public static void reflectTouchTargetInstance(View _view, String tag) {
        try {
            Field filed = _view.getClass().getSuperclass().getSuperclass().getDeclaredField("mFirstTouchTarget");
            filed.setAccessible(true);
            Object obj = filed.get(_view);
            Log.e(Constant.TAG, _view.getClass().getName() + " " + tag + "-------" + obj.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
// 子View
public class MyButton extends Button {
    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(Constant.TAG, this.getClass().getName() + " onTouchEvent + " + event.getAction());
        return super.onTouchEvent(event);
    }
}

运行结果

其中
MyLinearLayout onTouchEvent + 0
MyButton onTouchEvent + 0
MyLinearLayout onTouchEvent + 2
MyLinearLayout onInterceptTouchEvent-------android.view.ViewGroup$TouchTarget@41f7bc9
MyButton onTouchEvent + 3
MyLinearLayout onTouchEvent + 2
MyLinearLayout onTouchEvent + 2
MyLinearLayout onTouchEvent + 1
// 0 1 2 3分别代表
public static final int ACTION_DOWN             = 0;
public static final int ACTION_UP               = 1;
public static final int ACTION_MOVE             = 2;
public static final int ACTION_CANCEL           = 3;
  • mFirstTouchTaget
result = {ViewGroup$TouchTarget@10336} 
 child = {MyButton@10340} "MyButton{ead02f0 VFED..C.. ...P.... 0,0-277,96 #7f080315 app:id/show_fragment}"
 next = null
 pointerIdBits = 1
 shadow$_klass_ = {Class@2881} "class android.view.ViewGroup$TouchTarget"
 shadow$_monitor_ = -2078311479

结论

  • 当父容器拦截了子View的MOVE事件,会给子View发送一个ACTION_CANCEL事件
  • 当从当前View的MVOE事件的时候移动到其它区域该View也会收到ACTION_CANCEL事件
// 带着结论看一下源代码
public boolean dispatchTouchEvent(MotionEvent ev) { 
            if (!canceled && !intercepted) {
                // 不拦截事件的处理逻辑忽略
            }
            // Dispatch to touch targets.
            // 由于当前的事件是MOVE事件,mFirstTouchTarget肯定有值了,这一点从上面的log也可以验证
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        // 当前拦截 intercepted = true
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // taget.child = MyButton
                        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;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }
  • dispatchTransformedTouchEvent
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;
        }
    }

mFirstTarget的链表

// 获取当前DecorView
((Activity)_view.getContext()).getWindow().getDecorView()
result = {DecorView@10368} "DecorView@6fe3e02[MainActivity]"
 mFirstTouchTarget = {ViewGroup$TouchTarget@10432} 
 child = {LinearLayout@10416} "android.widget.LinearLayout{e16384e V.E...... ........ 0,0-720,1424}"
   mFirstTouchTarget = {ViewGroup$TouchTarget@10466} 
     child = {FrameLayout@10490} "android.widget.FrameLayout{bddb6f3 V.E...... ........ 0,55-720,1424}"
        mFirstTouchTarget = {ViewGroup$TouchTarget@10826} 
           child = {ActionBarOverlayLayout@11515} "androidx.appcompat.widget.ActionBarOverlayLayout{26242e3 V.E...... ........ 0,0-720,1369 #7f0800f8 app:id/decor_content_parent}"
                mFirstTouchTarget = {ViewGroup$TouchTarget@11537} 
                   child = {ContentFrameLayout@11524} "androidx.appcompat.widget.ContentFrameLayout{96c2a3f V.E  #1020002 android:id/content}"
                        mFirstTouchTarget = {ViewGroup$TouchTarget@11566} 
                             child = {NestedScrollView@11579} "androidx.core.widget.NestedScrollView{ef15ff8 VFED..... ........ 0,0-720,1257}"
                                  mFirstTouchTarget = {ViewGroup$TouchTarget@11593} 
                                       child = {LinearLayout@11604} "android.widget.LinearLayout{63f2237 V.E...... ........ 0,0-720,1355}"
                                            mFirstTouchTarget = {ViewGroup$TouchTarget@11610} 
                                                child = {ConstraintLayout@11619} "androidx.constraintlayout.widget.ConstraintLayout{324350d V.E...... ........ 0,0-720,1355}"
                                                     mFirstTouchTarget = {ViewGroup$TouchTarget@11629} 
                                                         child = {MyLinearLayout@10328} "MyLinearLayout{3dff6f9 V.E...... ........ 0,0-720,1355}"
                                                              mFirstTouchTarget = {ViewGroup$TouchTarget@10336} 
                                                                   child = {MyButton@10340} "MyButton{ead02f0 VFED..C.. ...P.... 0,0-277,96 #7f080315 app:id/show_fragment}"
                                                                   next = null
                                                                   pointerIdBits = 1
                                                         next = null
                                                         pointerIdBits = 1    
                                                 next = null
                                                 pointerIdBits = 1
                                       next = null
                             next = null
                             pointerIdBits = 1
                   next = null
           next = null
           pointerIdBits = 1
     next = null
     pointerIdBits = 1
 next = null

总结

  • 当ACTION_DOWN事件之后的事件传递都是寻找mFirstTouchTarget找到目标View来提高查找速度
  • 当父View拦截子View的MOVE事件之后,子View会收到ACTION_CANCEL事件做善后处理
  • 当前View的MVOE事件移出当前View的区域,当前View也会收到ACTION_CANCEL事件
  • ACTION_DOWN是事件序列的开始,所以父View不能要DOWN事件对子View进行拦截,否则子View无法收到事件
  • 子View需要在ACTION_DOWN事件告诉父View不要对自己进行事件拦截,其它事件被父View拦截了,无法到达到子View也无法禁止父View进行拦截了。

你可能感兴趣的:(View事件传递机制)