为了账号安全,请及时绑定邮箱和手机立即绑定

从源码来看事件传递机制

标签:
Android

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

以下如无特殊说明,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)

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

TIM图片20171008203104.png

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

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

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

本文将跳过非核心过程重点分析 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。

TIM图片20171008211838.png

所以接下来会走到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改变一下按下态。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消