(十八)事件分发-源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

事件分发在各个版本的 API 都有所改动,这边是基于 25 进行学习。

一、onClick 与 onTouch

先看一段简单的代码:

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyButton button = (MyButton) findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i(TAG, "onClick 事件");

            }
        });

        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                //return false 表示不消耗此事件,true 表示消耗
                Log.i(TAG, "onTouch 事件");
                return false;
            }
        });
    }
}

在代码中同时设置了 onClick 与 onTouch 的监听,onTouch 方法里面还有个返回值 boolean,false 表示不消耗此事件,则这次事件能继续往下传播;true 表示消耗此事件,不能触发 onClick 事件。先来分析下这里的源码是怎么写的,为什么为这样。

查看 setOnClickListener 和 setOnTouchListener 源码,默认是调用 View 里面的方法,把对应的监听存储在 View 下的 ListenerInfo 中。

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }
 public void setOnTouchListener(OnTouchListener l) {
        getListenerInfo().mOnTouchListener = l;
    }

点击事件最终会调用 View 下的 dispatchTouchEvent 方法,具体后面再讲,这边先查看 View 中的 dispatchTouchEvent 方法。

public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

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

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

在 dispatchTouchEvent 方法里面设置了一个 Boolean 变量 result ,初始值为 false。在32行的时候调用 mOnTouchListener 的 onTouch 方法,如果 onTouch 返回 true 的话,则把 result 设置为 true。继续往下, if (!result && onTouchEvent(event)) 中,!result 则为 false,所以后面的 onTouchEvent(event) 方法不执行。

我们看一下 View 中的 onTouchEvent(event),在 onTouchEvent(event) 中,当事件为 MotionEvent.ACTION_UP 的时候,调用 performClick(), performClick() 调用了 mOnClickListener 的 onClick 方法。

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

所以,当 mOnTouchListener 的 onTouch 方法返回 true 的话,result 值变为 true。 if (!result && onTouchEvent(event)) 中,!result 则为 false,后面的 onTouchEvent(event) 方法不执行,也就不会执行 mOnClickListener 的 onClick 方法。

注:onClick 是在 View 的 onTouchEvent(event) 方法中被调用,自定义控件如果重写了 onTouchEvent(event) 方法且不调用 super.onTouchEvent(event) 则一样不会调用到 onClick 方法。

二、ViewGroup 事件分发

当触摸屏幕的时候,底层通过传感器获取到触摸的坐标,底层通过 C 、C++ 调用到 PhoneWindow,PhoneWindow 会间接调用到 Activity 的 dispatchTouchEvent,这边就从 Activity 开始分析。

1.事件调用顺序

在事件分发中,主要有三个方法:onTouchEvent、onInterceptTouchEvent 和 dispatchTouchEvent。我们先来验证下这三个方法调用的顺序。

自定义 View:

public class MyButton extends View {
    private final String TAG = "MyButton";

    public MyButton(Context context) {
        super(context);
    }

    public MyButton(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public MyButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent 事件:" + event.getAction());
        return false;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i(TAG, "dispatchTouchEvent 事件:" + event.getAction());
        return super.dispatchTouchEvent(event);
    }
}

如果自定义 View 不重写 onTouchEvent 方法的话,默认调用 View 的 onTouchEvent 方法,该方法返回 true,则 ViewGroup 和 Activity 无法触发 onTouchEvent 方法。

自定义 ViewGroup:

public class MyViewGroup extends RelativeLayout {

    private final String TAG = "MyViewGroup";

    public MyViewGroup(Context context) {
        super(context);
    }

    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent 事件:" + event.getAction());
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(TAG, "onInterceptTouchEvent 事件:" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent 事件:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }
}

直接继承 RelativeLayout 是因为 RelativeLayout 里面没有对这三个方法进行重写,后面布局也方便。

MainActivity:

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyButton button = (MyButton) findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i(TAG, "onClick 事件");

            }
        });

        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                //return false 表示不消耗此事件,true 表示消耗

                Log.i(TAG, "onTouch 事件" + motionEvent.getAction());
                return false;
            }
        });
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i(TAG, "dispatchTouchEvent 事件:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "onTouchEvent 事件:" + event.getAction());
        return super.onTouchEvent(event);
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<com.xiaoyue.androidevent.MyViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xiaoyue.androidevent.MainActivity">

    <com.xiaoyue.androidevent.MyButton
        android:id="@+id/btn"
        android:background="@color/colorPrimary"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</com.xiaoyue.androidevent.MyViewGroup>

运行后点击一次结果:

09-12 22:22:29.734 5186-5186/com.xiaoyue.androidevent I/MainActivity: dispatchTouchEvent 事件:0
09-12 22:22:29.734 5186-5186/com.xiaoyue.androidevent I/MyViewGroup: dispatchTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MyViewGroup: onInterceptTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MyButton: dispatchTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MainActivity: onTouch 事件0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MyButton: onTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MyViewGroup: onTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MainActivity: onTouchEvent 事件:0
09-12 22:22:29.804 5186-5186/com.xiaoyue.androidevent I/MainActivity: dispatchTouchEvent 事件:1
09-12 22:22:29.804 5186-5186/com.xiaoyue.androidevent I/MainActivity: onTouchEvent 事件:1

可见这三个方法的顺序是:dispatchTouchEvent、onInterceptTouchEvent、onTouch。

2.Activity 的 dispatchTouchEvent

前面说底层通过 Phonewindow 间接调用到 Activity 的 dispatchTouchEvent。

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

当事件为 MotionEvent.ACTION_DOWN 的时候,会调用 onUserInteraction 方法,这是一个空的方法,是用来让用户重写的,这边可以忽略。

当 getWindow().superDispatchTouchEvent(ev) 这个返回 true 的时候,dispatchTouchEvent 也直接返回 true,这时候不调用 Activity 的 onTouchEvent 方法。

getWindow( ) 获取到的是 Window 这个抽象类,这边 Window 的实现类是 PhoneWindow(在 (一)UI绘制流程-源码分析 http://blog.csdn.net/qq_18983205/article/details/71601099 有提到)。

接着查看 PhoneWindow 的 superDispatchTouchEvent 方法。

   public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

直接调用 mDecor 的 superDispatchTouchEvent 方法,mDecor 是一个 DecorView,继续看 DecorView 中该方法的实现。

  public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

DecorView 直接调用父类的 dispatchTouchEvent,DecorView 的父类是 FrameLayout,但是 FrameLayout 没有 dispatchTouchEvent 这个方法,所以直接调用到 FrameLayout 的父类 ViewGroup 中的 dispatchTouchEvent 方法。

这边整理下流程:
这里写图片描述

3. ViewGroup 的 dispatchTouchEvent

dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
        //如果是其他触摸设备,比如触摸笔,则调用其他设备的 onTouchEvent
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        //是否有辅助功能
        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        //onFilterTouchEventForSecurity 进行一个事件的安全检查,正常都是 true
        if (onFilterTouchEventForSecurity(ev)) {
            //获取事件,计算 MASK,确认是 down、move 还是 up。
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            //对 down 事件的处理,对于一次触摸事件来说,down 事件是一个开始
            //在这边进行一些初始化的操作,比如清除手势,状态等
            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                //这里有个重要的操作是将 mFirstTouchTarget 置空,具体在下面注解,可以先看
                cancelAndClearTouchTargets(ev);
                //为了当前的触摸循环(从 down 开始)重置所有的触摸状态
                resetTouchState();
            }

            //intercepted 表示检查是否拦截,标记 ViewGroup 是否拦截该触摸事件
            // Check for interception.
            final boolean intercepted;

            //是 down 事件或者 mFirstTouchTarget 不等于空
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //disallowIntercept 的值表示是否不允许进行拦截 
                //true:子 View 不需要父视图拦截事件 
                //false:子 View 需要父视图拦截事件 
                // 可以通过viewGroup.requestDisallowInterceptTouchEvent(boolean) 对 disallowIntercept 的值进行设置
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

                //disallowIntercept 为true,ViewGroup 不调用 onInterceptTouchEvent 进行拦截
                if (!disallowIntercept) {
                    //事件传递流程:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent 中 onInterceptTouchEvent 在 dispatchTouchEvent之后的原因
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    //禁止拦截时,事件拦截标志位设置为 false
                    intercepted = false;
                }
            } else {
                //当前为 down 事件,并且 mFirstTouchTarget 为空是,设置 intercepted = true,表示改事件被 ViewGroup 拦截
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            //检查事件是否被取消了
            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;

            //事件分发。不是事件 cancel ,并且事件没有被拦截
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                //处理 down 事件,这里比较复杂,也是最重要的一块
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    //当前 ViewGroup 含有子控件
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);

                        //进行子控件重排序
                        //为了处理界面上重叠的两个 ViewGroup,重叠的两个都能接收到触摸事件,在上层的先处理,所以需要在这边进行子控件的重排序
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        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 there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            //辅助功能的处理
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            //当前 ViewGroup 不能接收事件或点击点不在该子控件范围内,直接跳过,进行下一个循环
                            //canViewReceivePointerEvents 方法可以看出,不能接收事件的有:处于动画执行过程中、子控件不可见
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            //执行到这里,子控件决对可以接收事件

                            //第一次 down 的时候 newTouchTarget 为空,在 move 的时候 newTouchTarget 不为空,这时候会去直接找到接收点击事件的 View(move 动作是触发多次的,这样就可以不必每次去遍历寻找)
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                //找到接收触摸事件的子 View,跳出循环
                                // 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);

                            //dispatchTransformedTouchEvent 是真正做事件分发的方法
                            //将触摸事件传递给子 View,进行递归调用 dispatchTouchEvent 方法
                            //在 dispatchTouchEvent 中,如果子 View 为 ViewGroup,继续递归调用ViewGroup 的 dispatchTouchEvent,如果子 View 为 View 的话,调用 View 的 dispatchTouchEvent,即我们最上面讲的,View 的 dispatchTouchEvent 会调用 View 的 onTouchEvent
                            //dispatchTransformedTouchEvent 返回 true,表示子 View 接收了触摸事件
                            //dispatchTransformedTouchEvent 返回 false 的话,addTouchTarget 方法不被执行,导致 mFirstTouchTarget 为空,所以当前的子 View 无法处理后续的 move 和 up 事件
                            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 开始赋值
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                //alreadyDispatchedToNewTouchTarget 表示触摸事件已经分发给新的 TouchTarget
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    //到这里来还没有找到子 View 接收触摸事件并且 mFirstTouchTarget 不为空  
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        //newTouchTarget 指向了最初的 TouchTarget    
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            //up 和 move 不会经过上面那复杂的逻辑,直接从这开始
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //mFirstTouchTarget 为空,说明该触摸事件还没有被任何一个子 View 接收,此时 ViewGroup 会像一个 View 一样去调用 dispatchTouchEvent(即在 dispatchTouchEvent 方法中调用 onTouchEvent 方法,注意第三个参数这时候是 null)
                //所以,如果子 View 的 onTouchEvent 方法返回 true 的时候,则他的上一次 ViewGroup 的 onTouchEvent 不会被执行
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //mFirstTouchTarget 不为空,说明该触摸事件被子 View 接收了,这样该子 View 可以继续处理后续的 move 和 up 事件
                // 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;
                    //这里主要处理非 down 事件时候,进行递归调用 dispatchTransformedTouchEvent
                    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;
                }
            }

            //up 和 cancle 的时候,
            // 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;
    }

在 ViewGroup 里,通过递归,最终调到我们触摸的自定义 ViewGroup 的 dispatchTouchEvent,自定义的 ViewGroup 调用 super.dispatchTouchEvent,根据上面源码分析,这时候调用到自定义 ViewGroup 的 onInterceptTouchEvent 方法。

继续往下递归一次,则调用到自定义 View 的 dispatchTouchEvent 方法。

cancelAndClearTouchTargets

   private void cancelAndClearTouchTargets(MotionEvent event) {
        //如果是第一次触发 down 的时候,mFirstTouchTarget 为空,直接跳过
        if (mFirstTouchTarget != null) {
            boolean syntheticEvent = false;
            if (event == null) {
                final long now = SystemClock.uptimeMillis();
                event = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
                syntheticEvent = true;
            }

            //TouchTarget 是 ViewGroup 的一个内部类,记录多手指操作(最多32)的一个单向链表。封装了被触摸的 View 及这次触摸所对应的ID
            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                resetCancelNextUpFlag(target.child);
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
            //清除 mFirstTouchTarget,以及把 mFirstTouchTarget 置空
            clearTouchTargets();

            if (syntheticEvent) {
                event.recycle();
            }
        }
    }

clearTouchTargets

    private void clearTouchTargets() {
        //全部置为初始化状态
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

dispatchTransformedTouchEvent
真正做事件分发的。会调用子控件的 dispatchTouchEvent,形成递归,最终调用到触发事件的自定义 View 的 dispatchTouchEvent。

dispatchTransformedTouchEvent 会调用递归调用 dispatchTouchEvent 最终调用到 onTouchEvent
所以 dispatchTransformedTouchEvent 的返回值实际上是由 onTouchEvent 决定的.。

比较重要是第三个参数有时候为 null。当 child 为空的时候,调用 super.dispatchTouchEvent(event),这调用的是 View 的 dispatchTouchEvent 方法,在最开始就讲了,View 的 dispatchTouchEvent 会调用 onTouchEvent 方法。child 不为空的时候,开始递归,直到 child 是一个 View,最终也会调用到 onTouchEvent 方法。

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();
        //处理 cancle 事件
        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;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    //调 View 的 dispatchTouchEvent
                    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);
        }

        // Perform any necessary transformations and dispatch.
        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());
            }

            //调用子控件的 dispatchTouchEvent
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

注:在 dispatchTouchEvent 源码上提到:通过viewGroup.requestDisallowInterceptTouchEvent(boolean) 对 disallowIntercept 的值进行设置。

        if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

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

这里要注意的是,当事件为 down 的时候, resetTouchState 会重置 mGroupFlags 值,导致disallowIntercept 会一直为 false,即一定会进行过滤。所以要设置不拦截只能在 down 事件之后设置父控件不在继续进行拦截。那么想要在 down 事件不进行拦截,只能在 onInterceptTouchEvent 的 down 事件处理返回 false。

4.流程总结

这边直接上一个流程图:
这里写图片描述

这个是个人的理解,返回的箭头表示方法执行完后的返回。如果有多层 ViewGroup,是跟画出来 ViewGroup 一样的流程再加一层。

网络上现在很多都是画成 U 形,在 View 的 onTouchEvent 直接回调到 ViewGroup 的 onTouchEvent,从源代码上看肯定不是这样的,onTouchEvent 只有在 View 类的 dispatchTouchEvent 方法中被调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值