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

View事件体系之事件的分发

标签:
Android

事件的分发流程

Activity->window->ViewGroup(decor View)

dispathTouchEvent(属于Activity)

决定是否Activity要自己消费事件,首先将事件交由依附ActivityWindow进行分发,如果View消费了事件,返回true,那么分发过程结束,如果所有的View都没有消费事件,那么AvtivityonTouchEvent()会被调用.

decorView 分发过程

顶级View(ViewGroup)对View的分发包括几个过程:
点击事件到达-> 调用dispatchTouchEvent()->如果interecptTouchEvent()返回true,消费事件->a 否则 ->b

a.如果有onTouchListener(),则 onTouch()会被触发,否则onTouchEvent()会被调用,如果在onTouchEvent中设置了onClickListener,onCLick会被调用.

b.事件会传递给事件链上的子View,子View接收到事件调用dispatchTouchEvent().

事件链是一开始ViewGroup就会遍历所有的子View来判断哪些View可以接受到事件,主要根据 子View是否在播动画 和 点击坐标是否位于子View上.


interecptTouchEvent

并非每次都会调用,只有当 事件是ACTION_DOWN 且 mFirstTouchTarget != null(交由子View处理) 且 FLAG_DISALLOW_INTERCEPT 没被设置 时调用 onInterecptTouchEvent.

因为ACTION_DOWN基本上都是一个新的事件,这个时候肯定没有交由子View来处理,所以mFirstTouchTarget == null,再加上前面就有代码对FLAG_DISALLOW_INTERCEPT进行初始化,所以如果是ACTION_DOWN,onInterecptTouchEvent一定会被调用,但是decorView(Viewgroup)默认都不会拦截事件.因此子View不能影响decorViewACTION_DOWN的处理.

如果 事件是ACTION_DOWN 且 mFirstTouchTarget != null(交由当前View处理) 都不成立,即是收到ACTION_MOVE,ACTION_UP且已经标记不拦截事件,交由子View处理了就直接交由子View,不再拦截.
当消费事件返回true,不消费返回false.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
public boolean dispatchTouchEvent(MotionEvent ev) {
   ...
   /**
   见 上一节  `interecptTouchEvent`
   */
        // 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;
        }
      ...
      /**
      分发之核心,遍历所有的子View来判断哪些View可以接受到事件,主要根据 子View是否在播动画 和 点击坐标是否位于子View上.
      */
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);
                        ...
                        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();
                }
        /**
        如果所有的View都没有消费事件,那么ViewGroup将会自己消费事件;
        否则继续处理后续事件(ACTION_UP,ACTION_MOVE)的分发,还有可能出现的取消点击(在屏幕上滑动离开目标View)
        不再判断,直接将点击事件传给目标View
        */
        // 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;
            }
        }
...
}

View对点击事件的处理

以上都是点击事件怎么从Activity->Window->decor View
但是大多数情况下,处理点击事件的是View.

具体过程已经写了注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
    /**
    这里是View处于不可DISABLE的状态,但是仍然会消费事件.
    前提是view是CLICKABLE 或者 LONG_CLICKABLE
    */
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return (((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
    }
    /**
    如果View有设置代理,会执行mTouchDelegate.onTouchEvent(event)
    */
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    /**
    如果View是CLICKABLE或者LONG_CLICKABLE,就消费事件.
    */
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            /**
            如果`ACTION_UP`,说明一次点击事件结束,`performClick()`方法被调用,它调用它内部的`click()`方法.
            */
            case MotionEvent.ACTION_UP:
             ...
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
            ...
                break;
            /**
            如果是`ACTION_DOWN`的情况,考虑是不是滑动操作.如果是就进行滑动操作的初始化;
            否则调用`setPressed()`来设置当前的按压状态->因为:
            事件分发到这里就以及确定了哪个`View`消费了事件,这个时候就要把这个消息往回传,告诉高等级那些大人物们.
            回传的起点自然就是当前所在的`view`的`onTouchEvent`->`dispatchSetPressed(pressed)`,
            让整个view都知道事件消费情况,并且返回`result`告诉viewGroup
            */
            //ViewGroup#dispatchTransformedTouchEvent()
             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;
                    }
            /**
            而ViewGroup#dispatchTouchEvent()收到返回值是这样的
            */
            //ViewGroup#dispatchTouchEvent()
             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                         ...
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
            /**
            这是一个循环的一部分,这个循环遍历child(子View),用`dispatchTransformedTouchEvent()`进行分发;
            如果分发未果,没有消费事件,就遍历下一个子View;
            如果分发成功执行其内部操作,可以看到,已经分发成功,并获取到目标`View`.
            */
            case MotionEvent.ACTION_DOWN:
                ...
                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                  ...
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                        /**
                         * Sets the pressed state for this view and provides a touch coordinate for
                         * animation hinting.
                         *
                         * @param pressed Pass true to set the View's internal state to "pressed",
                         *            or false to reverts the View's internal state from a
                         *            previously set "pressed" state.
                         * @param x The x coordinate of the touch that caused the press
                         * @param y The y coordinate of the touch that caused the press
                         */
                    setPressed(true, x, y);
                    checkForLongClick(0);
                }
                break;
                /**
                撤销动作,算作没有消费事件
                */
            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
              ...
                break;
                /**
                移动
                */
            case MotionEvent.ACTION_MOVE:
              ...
                break;
        }
        return true;
    }
    return false;
}

事件分发-恶魔法则11条

1.同一序列事件是指从手指接触屏幕开始,到手指离开屏幕结束,中间产生的一系列以ACTION_DOWN开始,中间N个ACTION_MOVE,最后以ACTION_UP结束的一系列事件.

2.某个View一旦决定拦截事件(拦截了ACTION_DOWN),那么剩下的ACTION_MOVE & ACTION_UP都只能交由它处理,并且它的onInterceptTouchEvent()不再会被调用(其实如果是子View的话,decor View的也不会再被调用).

3.一个事件只能被一个View拦截消耗,因为后续不会再有拦截,也就没机会交给其它View处理.

4.某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false)那么同一事件序列中的其它事件都不会再交由它处理, 会交由他们的父元素处理(Activity),既父元素的onTouchEvent会被调用.

5.如果View不消耗ACTION_DOWN以外的事件,这个点击事件会消失,父元素的onTouchEvent不会被调用,最后这些消失的点击事件会传给Activity处理.

6.ViewaGroup默认不拦截任何事件.

7.View没有onInterceptTouchEvent,事件传递给它后它的onTouchEvent就会被调用.

8.View的onTouchEvent默认消耗事件,除非它的CLICKABLELONG_CLICKABLE都为false.

9.View的ENABLE不影响事件的消费,只有CLICKABLE影响.

10.onClick会发生的前提是View是可点击的,并且收到了一系列事件(ACTION_DOWN->ACTION_UP),并且设置了监听(listener).

11.事件总是先传给父元素再分发给子View,可以通过requestDissallowInterceptTouchEvent来干预父元素除了ACTION_DOWN事件的分发.\

原文链接:http://www.apkbus.com/blog-705730-62507.html

点击查看更多内容
TA 点赞

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

评论

作者其他优质文章

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

100积分直接送

付费专栏免费学

大额优惠券免费领

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

举报

0/150
提交
取消