从源码来看事件传递机制

这次没啥好说的,就是从源码层面看事件传递机制

以下如无特殊说明,api源码版本是api25

1. MainActivity 界面的Xml如下

<?xml version="1.0" encoding="utf-8"?>
<com.zjw.appmethodtime.MyRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/myRl"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.zjw.appmethodtime.MainActivity">

    <com.zjw.appmethodtime.MyLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <com.zjw.appmethodtime.MyTextView
            android:id="@+id/text_view0"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/colorPrimary"
            android:text="Click Me 0"
            android:gravity="center"
            android:textSize="25sp"
            android:textStyle="bold"/>

        <com.zjw.appmethodtime.MyTextView
            android:id="@+id/text_view"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/colorPrimary"
            android:text="Click Me"
            android:gravity="center"
            android:textSize="25sp"
            android:textStyle="bold"/>
    </com.zjw.appmethodtime.MyLayout>
</com.zjw.appmethodtime.MyRelativeLayout>
复制代码

MainActivity代码如下 TextView.onTouchEvent 11215行下断点就行,这里点击的是第二个TextView,id 为text_view(完整代码参考上上篇)

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    protected MyRecycleView mListView;
    protected TextView mTextView;
    protected MyRelativeLayout mMyRl;
    protected MyTextView mTextView0;
    private float value;

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

    private void initView() {
        Resources resources = this.getResources();
        value = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, resources.getDisplayMetrics());
        mTextView = (TextView) findViewById(R.id.text_view);
        mTextView.setOnClickListener(MainActivity.this);
        mTextView0 = (MyTextView) findViewById(R.id.text_view0);
        mTextView0.setOnClickListener(MainActivity.this);
        mMyRl = (MyRelativeLayout) findViewById(R.id.myRl);
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.text_view) {
            view.getLayoutParams().height += value;
            //shouldLocalIinvalidate 为true 表示开启局部刷新 否则为关闭(MyLayout shouldLocalIinvalidate 默认为false)
            ((MyLayout) view.getParent()).shouldLocalIinvalidate = true;
            view.requestLayout();
            view.invalidate();
            //局部刷新完成及时恢复成可以全局刷新的状态
            ((MyLayout) view.getParent()).shouldLocalIinvalidate = false;
        } else if (view.getId() == R.id.text_view0) {

        }

    }
}
复制代码

然后断点堆栈如下(太多了 不好截图,所以转成文字了)

  at android.view.View.onTouchEvent(View.java:11215)
	  at android.widget.TextView.onTouchEvent(TextView.java:8460)
	  at android.view.View.dispatchTouchEvent(View.java:10023)
	  at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
	  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
	  at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
	  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
	  at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
	  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
	  at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
	  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
	  at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
	  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
	  at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
	  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
	  at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2632)
	  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2321)
	  at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:413)
	  at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1808)
	  at android.app.Activity.dispatchTouchEvent(Activity.java:3061)
	  at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
	  at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:375)
	  at android.view.View.dispatchPointerEvent(View.java:10243)
	  at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4438)
	  at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4306)
	  at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
	  at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3906)
	  at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3872)
	  at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3999)
	  at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3880)
	  at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4056)
	  at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
	  at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3906)
	  at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3872)
	  at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3880)
	  at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3853)
	  at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6246)
	  at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6220)
	  at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6181)
	  at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6349)
	  at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
	  at android.os.MessageQueue.nativePollOnce(MessageQueue.java:-1)
	  at android.os.MessageQueue.next(MessageQueue.java:323)
	  at android.os.Looper.loop(Looper.java:136)
	  at android.app.ActivityThread.main(ActivityThread.java:6119)
	  at java.lang.reflect.Method.invoke(Method.java:-1)
	  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
	  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
复制代码

对应最后执行的行数为下图所示

这就是一次点击事件所触发的事件传递,再到执行view的performClick执行到onClick回调函数。

2. 下面重点分析一下事件传递机制的流程

如上文堆栈信息来看,事件响应是自顶向下传递的,ViewRootImpl -》DecorView -》Activity -》 DecorView -》子ViewGroup(多次)-》再到目标子View

本文将跳过非核心过程重点分析 ViewGroup(多次)-》再到目标子View 这个段过程,也就是重点分析ViewGroup类
dispatchTouchEvent方法(内部调用dispatchTransformedTouchEvent方法) ViewGroup类dispatchTouchEvent源码如下

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
       .//省略掉不重要的
           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;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // 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;
                            }

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

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

                    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.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            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;
                }
            }

    //省略掉不重要的
        return handled;
    }
复制代码

解析一下,从if (actionMasked == MotionEvent.ACTION_DOWN这里开始其实目的就是找到newTouchTarget,这段if就是遍历找到 down事件落在该viewgroup的那个直接子view或者直接子ViewGroup,同时如果那个直接子view或者直接子ViewGroup是可接受事件并且该down事件坐标落点在该区域内,, 给具体详见ViewGroup类2249行的

 if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
复制代码

然后则通过newTouchTarget = addTouchTarget(child, idBitsToAssign);(ViewGroup2280行),给ViewGroup的mFirstTouchTarget赋值为找到的那个直接子view或者直接子ViewGroup,接下来走到第一个dispatchTransformedTouchEvent方法,也就是ViewGroup的2264行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)

当前ViewGroup是 MyLayout,而mFirstTouchTarget在前面代码中已经赋值。通过debug观察mFirstTouchTarget可以发现mFirstTouchTarget对象的child字段正是第二个TextView,id 为text_view。

所以接下来会走到ViewGroup的dispatchTransformedTouchEvent函数2632行

 handled = child.dispatchTouchEvent(event);
复制代码

递归调用子ViewGroup或者直接调用子View的dispatchTouchEvent函数 接下来看到2305行,代码为

  if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
复制代码

因为mFirstTouchTarget 不为null所以走到 else分支接下来走到2317行

 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    }
复制代码

之后dispatchTouchEvent函数MotionEvent 为Down 事件的时候 返回值为true 递归结束,到此完成目标。

之后响应move 和 up 事件 不会给mFirstTouchTarget重新赋值,所以会走2321行代码中的dispatchTransformedTouchEvent函数

 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) {
复制代码

3. 总结

估计大家都比较懵逼,总结一下吧ViewGroup类的dispatchTouchEvent方法代码顺序有三处 dispatchTransformedTouchEvent,但是根据不同条件只会调用其中的一次。(以下暂不做cancle事件情况讨论)

  1. 当有visible状态同时event落点在该区域的直接子view或者直接子ViewGroup存在 ,时同时event类型是Down,则调用第一个出现的dispatchTransformedTouchEvent (ViewGroup 2264行),然后dispatchTouchEvent 方法return true(因为textView的dispatchTouchEvent 方法调用了onTouchEvent消费了该次事件)递归结束

  2. 当有visible状态同时event落点在该区域的直接子view或者直接子ViewGroup存在,但是event类型不是Down,则调用第三个出现的dispatchTransformedTouchEvent (ViewGroup 2321行),然后dispatchTouchEvent 方法return true(因为textView的dispatchTouchEvent 方法调用了onTouchEvent消费了该次事件)递归结束

  3. 当无(visible状态同时event落点在该区域的)直接子view或者直接子ViewGroup存在,则调用第二个dispatchTransformedTouchEvent (ViewGroup 2307行),,然后dispatchTouchEvent 方法return false递归结束

4. 扩展小知识

第一就是 关于事件传递 只在down 做了 落点范围,确定了 要把后续事件交给哪个view处理,move 和 up 的落点,可以不在该view区域内 第二个就是 关于 onclick 事件 是必须 down move up 均在 该view范围内才行,也就是说 down 在 该view区域内 但是 某一次move 到了外面,则不会触发 click事件 ,但是会调用一次 setPressed改变一下按下态。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值