美文网首页
ViewGroup Touch 源码解析

ViewGroup Touch 源码解析

作者: dengzi_android | 来源:发表于2017-09-11 13:02 被阅读0次

    1、现象分析

    我们分别定义四个自定义view

    // 如果是继承RelativeLayout则实现其三个方法,并输出log
    // dispatchTouchEvent
    // onInterceptTouchEvent
    // onTouchEvent
    public class MyRelaLayout1 extends RelativeLayout{}
    
    public class MyRelaLayout2 extends RelativeLayout {}
    
    // 如果是继承TextView则实现其两个方法,并输出log
    // dispatchTouchEvent
    // onTouchEvent
    public class MyTextView1 extends TextView {}
    
    public class MyTextView2 extends TextView {}
    

    写一个相互嵌套的布局

        <com.djk.test.touch.MyRelaLayout1
            android:id="@+id/p_1"
            android:layout_width="200dp"
            android:layout_height="200dp">
    
            <com.djk.test.touch.MyRelaLayout2
                android:id="@+id/p_2"
                android:layout_width="200dp"
                android:layout_height="200dp">
    
                <com.djk.test.touch.MyTextView1
                    android:id="@+id/c_1"
                    android:layout_width="150dp"
                    android:layout_height="150dp"
                    android:background="#000000" />
    
                <com.djk.test.touch.MyTextView2
                    android:id="@+id/c_2"
                    android:layout_width="100dp"
                    android:layout_height="100dp"
                    android:background="#ff0000" />
    
            </com.djk.test.touch.MyRelaLayout2>
    
        </com.djk.test.touch.MyRelaLayout1>
    

    实现效果如下图,MyTextView1为黑色背景,MyTextView2为红色背景,并覆盖在MyTextView1之上

    Paste_Image.png

    下面我们来进行操作来分析touch事件的传递与拦截
    现象一:就现在的默认情况下,我们点击红色区域

    -> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
    -> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down
    -> MyTextView2.dispatchTouchEvent.Down -> MyTextView2.onTouchEvent.Down
    -> MyTextView1.dispatchTouchEvent.Down -> MyTextView1.onTouchEvent.Down
    -> MyRelaLayout2.onTouchEvent.Down 
    -> MyRelaLayout1.onTouchEvent.Down
    

    我们得到touch事件从MyRelaLayout1-> MyRelaLayout2-> MyTextView2-> MyTextView1
    可以分析得出touch传递是从最外层的MyRelaLayout1传到第二层MyRelaLayout2
    在RelativeLayout布局中,MyTextView2 和MyTextView1属于平等关系,那么最上层子view先拿到touch事件,然后再传递到下面的子view

    现象二:我们给MyTextView2设置onTouchEvent事件return true

    -> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
    -> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down
    -> MyTextView2.dispatchTouchEvent.Down -> MyTextView2.onTouchEvent.Down
    // ... Move
    -> MyRelaLayout1.dispatchTouchEvent.Up -> MyRelaLayout1.onInterceptTouchEvent.Up
    -> MyRelaLayout2.dispatchTouchEvent.Up -> MyRelaLayout2.onInterceptTouchEvent.Up
    -> MyTextView2.dispatchTouchEvent.Up -> MyTextView2.onTouchEvent.Up
    

    可以看出我们成功的把MyTextView1的所有事件都截断了

    现象三:我们给MyRelaLayout2设置onInterceptTouchEvent事件return true

    -> MyRelaLayout1.dispatchTouchEvent.Down -> MyRelaLayout1.onInterceptTouchEvent.Down
    -> MyRelaLayout2.dispatchTouchEvent.Down -> MyRelaLayout2.onInterceptTouchEvent.Down -> MyRelaLayout2.onTouchEvent.Down
    -> MyRelaLayout1.dispatchTouchEvent.Up -> MyRelaLayout1.onInterceptTouchEvent.Up
    -> MyRelaLayout2.dispatchTouchEvent.Up -> MyRelaLayout2.onTouchEvent.Up
    

    可以看出,成功将后面所有的事件都截断了,并调用了自己的onTouchEvent方法

    2、源码分析(android-25)

        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) {
                    // 清除touch targets  其核心代码就是  mFirstTouchTarget = null;
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
    
                // 是否要拦截
                final boolean intercepted;
                // 如果是Down事件,或 mFirstTouchTarget != null;
                if (actionMasked == MotionEvent.ACTION_DOWN 
                        || mFirstTouchTarget != null) {
                    // 子类请求不要拦截事件
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    // 子类没有请求不要拦截事件,走我们正常的流程
                    if (!disallowIntercept) {
                        // 调用自己的onInterceptTouchEvent方法来判断是否要拦截touch事件 默认情况下返回 fase
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        intercepted = false;
                    }
                } else {
                    intercepted = true;
                }
    
                // ...
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                // 没有取消 & 没有拦截事件 正常情况下if能够执行
                if (!canceled && !intercepted) {
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        // ...
                        final int childrenCount = mChildrenCount;
                        if (newTouchTarget == null && childrenCount != 0) {
                            // ...
                            // 获取其子view的集合
                            final View[] children = mChildren;
                            // 反序for循环,获取子view(这里就是RelativeLayout中多层覆盖的情况下,首先拿到最上层的子view)
                            for (int i = childrenCount - 1; i >= 0; i--) {
                                final int childIndex = getAndVerifyPreorderedIndex(
                                        childrenCount, i, customOrder);
                                final View child = getAndVerifyPreorderedView(
                                        preorderedList, children, childIndex);
    
                                // ...
    
                                newTouchTarget = getTouchTarget(child);
                                // ...
                                // 详见下面的第二个代码块,如果dispatchTouchEvent方法返回true 则能进入这个方法
                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                    // ...
                                    // addTouchTarget() 方法核心代码 mFirstTouchTarget = target; 这里将 mFirstTouchTarget 赋值
                                    // 然后将mFirstTouchTarget 的值再赋值给 newTouchTarget
                                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                    alreadyDispatchedToNewTouchTarget = true;
                                    break;
                                }
                            }
                            if (preorderedList != null) preorderedList.clear();
                        }
                    }
                }
    
                // 这里如果mFirstTouchTarget = null,则去调用代码块2的方法,传入的child为null,这个时候就会调用自己的onTouchEvent方法
                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 {
                            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;
                    }
                }
    
                // 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;
        }
    
    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);
                // 如果child 为null,则去调用父类的dispatchTouchEvent,ViewGroup的父类也是View,
                // 所以相当于调用了View的dispatchTouchEvent,进而会调用自己的onTouchEvent方法
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {// 如果child 不为null,则去调用子类的dispatchTouchEvent,子类的touch事件从这里开始
                    handled = child.dispatchTouchEvent(event);
                }
                event.setAction(oldAction);
                return handled;
            }
    
            return handled;
        }
    

    3、源码变通(android源码中,所有的touch事件都会调用dispatchTouchEvent方法,其实我们可以将它拆成Down事件和其他事件来分析)

    第一步,当前是Down事件:

        public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean handled = false;
            mFirstTouchTarget = null;
            boolean intercepted = false;
            // 调用自己的onInterceptTouchEvent方法来判断是否要拦截touch事件 默认情况下返回 false
            intercepted = onInterceptTouchEvent(ev);
            // 这里如果是不拦截touch事件
            if (!intercepted) {
                // 获取其子view的集合
                final View[] children = mChildren;
                // 反序for循环,获取子view(这里就是RelativeLayout中多层覆盖的情况下,首先拿到最上层的子view)
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final View child = getChildView(i);
                    // 调用子类的dispatchTouchEvent,如果返回true则将 mFirstTouchTarget 赋值
                    handled = child.dispatchTouchEvent(event);
                    if (handled) {
                        // 这里将 mFirstTouchTarget 赋值
                        mFirstTouchTarget = target;
                        // 跳出循环,此后的view就收不到touch事件了
                        break;
                    }
                }
            }
            if (mFirstTouchTarget == null) {
                // 如果是mFirstTouchTarget == null,则说明没有一个子类返回true,
                // 则直接调用View的dispatchTouchEvent,进而会调用自己的onTouchEvent方法
                handled = super.dispatchTouchEvent(event);
            } 
            return handled;
        }
    

    第二步,当前是 Move or Up 事件:

        public boolean dispatchTouchEvent(MotionEvent ev) {
            // ...
            boolean handled = false;
    
            // 是否要拦截
            final boolean intercepted;
            // 这里的 mFirstTouchTarget 是由Down事件记录的
            if (mFirstTouchTarget != null) {
                // 调用自己的onInterceptTouchEvent方法来判断是否要拦截touch事件 默认情况下返回 fase
                intercepted = onInterceptTouchEvent(ev);
            } else {// 如果Down事件记录的 mFirstTouchTarget == null; 则拦截判断直接置为 true
                intercepted = true;
            }
            // 这里如果是不拦截touch事件
            if (!intercepted) {
                // 获取其子view的集合
                final View[] children = mChildren;
                // 反序for循环,获取子view(这里就是RelativeLayout中多层覆盖的情况下,首先拿到最上层的子view)
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final View child = getChildView(i);
                    // 调用子类的dispatchTouchEvent,如果返回true则将 mFirstTouchTarget 赋值
                    handled = child.dispatchTouchEvent(event);
                    if (handled) {
                        // 这里将 mFirstTouchTarget 赋值
                        mFirstTouchTarget = target;
                        // 跳出循环,后面的view就收不到touch事件
                        break;
                    }
                }
            }
            if (mFirstTouchTarget == null) {
                // 如果是mFirstTouchTarget == null,则说明没有一个子类返回true,
                // 则直接调用View的dispatchTouchEvent,进而会调用自己的onTouchEvent方法
                handled = super.dispatchTouchEvent(event);
            }
            return handled;
        }
    
    Paste_Image.png

    4、下面来总结一下吧
    ①、ACTION_DOWN事件为一个事件序列的开始,中间有若干个ACTION_MOVE,最后以ACTION_UP结束。
    ②、一个clickable或者longClickable的View会永远消费Touch事件,不管他是enabled还是disabled的
    ③、Touch事件是从最顶层的View一直分发到手指touch的最里层的View,如果最里层View消费了ACTION_DOWN事件(设置onTouchListener,并且onTouch()返回true 或者onTouchEvent()方法返回true)才会触发ACTION_MOVE,ACTION_UP的发生,如果某个ViewGroup拦截了Touch事件,则Touch事件交给ViewGroup处理
    ④、如果某一个View拦截了事件,那么同一个事件序列的其他所有事件都会交由这个View处理,此时不再调用View(ViewGroup)的onIntercept()方法去询问是否要拦截了。

    相关文章

      网友评论

          本文标题:ViewGroup Touch 源码解析

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