美文网首页
View事件传递机制

View事件传递机制

作者: 风逝_c609 | 来源:发表于2021-03-03 17:43 被阅读0次
    • 事件传递每次都要递归查找处理事件的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事件传递机制

          本文链接:https://www.haomeiwen.com/subject/bseqqltx.html