回答

收藏

[资料] 飞凌分享:Android之Touch事件分发机制

飞凌嵌入式 飞凌嵌入式 2856 人阅读 | 0 人回复 | 2014-01-24

本帖最后由 forlinx2013 于 2014-1-24 08:59 编辑

欢迎大家来到飞凌爱板网专区,对嵌入式技术感兴趣的朋友不妨多多关注一下,我们提供了公司所有开发板的所有资料,也会更新大量技术文章,欢迎大家一块学习提高!!!

直接进入正题,在android开发中,手势触碰是经常使用到的。这两天刚好在看这方面的资料,在这里写篇文章做个小总结。
首先必须知道五大布局LayoutLineaLayoutRelativeLayoutFrameLayoutAbsoluteLayout都继承自ViewGroup,而TableLayout继承LinearLayout,也相当于继承于ViewGroup
ViewGroup继承自View
而我们最终在屏幕上显示控制的控件如TextViewButton等都直接或间接继承自View
View中有两个方法参与到Touch事件分发
onDispatchTouchEvent(MotionEvent event)onTouch(MotionEvent event)
ViewGroup有三个方法参与到Touch事件分发
onDispatchTouchEvent(MotionEvent event)onInterceptTouchEvent(MotionEvent ev)onTouch(MotionEvent event)

当我们手指出门屏幕,手势事件最早被传递到Layout布局的dispatchTouchEvent,即ViewGroupdispatchTouchEvent

1 public boolean dispatchTouchEvent(MotionEvent ev) {  
2     final int action = ev.getAction();  
3     final float xf = ev.getX();  
4     final float yf = ev.getY();  
5     final float scrolledXFloat = xf + mScrollX;  
6     final float scrolledYFloat = yf + mScrollY;  
7     final Rect frame = mTempRect;  
8     boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
9     if (action == MotionEvent.ACTION_DOWN) {  
10         if (mMotionTarget != null) {  
11             mMotionTarget = null;  
12         }  
13         if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
14             ev.setAction(MotionEvent.ACTION_DOWN);  
15             final int scrolledXInt = (int) scrolledXFloat;  
16             final int scrolledYInt = (int) scrolledYFloat;  
17             final View[] children = mChildren;  
18             final int count = mChildrenCount;  
19             for (int i = count - 1; i >= 0; i--) {  
20                 final View child = children;  
21                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
22                         || child.getAnimation() != null) {  
23                     child.getHitRect(frame);  
24                     if (frame.contains(scrolledXInt, scrolledYInt)) {  
25                         final float xc = scrolledXFloat - child.mLeft;  
26                         final float yc = scrolledYFloat - child.mTop;  
27                         ev.setLocation(xc, yc);  
28                         child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
29                         if (child.dispatchTouchEvent(ev))  {  
30                             mMotionTarget = child;  
31                             return true;  
32                         }  
33                     }  
34                 }  
35             }  
36         }  
37     }  
38     boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
39             (action == MotionEvent.ACTION_CANCEL);  
40     if (isUpOrCancel) {  
41         mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
42     }  
43     final View target = mMotionTarget;  
44     if (target == null) {  
45         ev.setLocation(xf, yf);  
46         if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
47             ev.setAction(MotionEvent.ACTION_CANCEL);  
48             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
49         }  
50         return super.dispatchTouchEvent(ev);  
51     }  
52     if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
53         final float xc = scrolledXFloat - (float) target.mLeft;  
54         final float yc = scrolledYFloat - (float) target.mTop;  
55         mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
56         ev.setAction(MotionEvent.ACTION_CANCEL);  
57         ev.setLocation(xc, yc);  
58         if (!target.dispatchTouchEvent(ev)) {  
59         }  
60         mMotionTarget = null;  
61         return true;  
62     }  
63     if (isUpOrCancel) {  
64         mMotionTarget = null;  
65     }  
66     final float xc = scrolledXFloat - (float) target.mLeft;  
67     final float yc = scrolledYFloat - (float) target.mTop;  
68     ev.setLocation(xc, yc);  
69     if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
70         ev.setAction(MotionEvent.ACTION_CANCEL);  
71         target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
72         mMotionTarget = null;  
73     }  
74     return target.dispatchTouchEvent(ev);  
75 }  
public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    final float xf = ev.getX();
    final float yf = ev.getY();
    final float scrolledXFloat = xf + mScrollX;
    final float scrolledYFloat = yf + mScrollY;
    final Rect frame = mTempRect;
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (action == MotionEvent.ACTION_DOWN) {
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            ev.setAction(MotionEvent.ACTION_DOWN);
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
            for (int i = count - 1; i >= 0; i--) {
                final View child = children;
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                        || child.getAnimation() != null) {
                    child.getHitRect(frame);
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                        if (child.dispatchTouchEvent(ev))  {
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
    }
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    final View target = mMotionTarget;
    if (target == null) {
        ev.setLocation(xf, yf);
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        }
        return super.dispatchTouchEvent(ev);
    }
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        ev.setAction(MotionEvent.ACTION_CANCEL);
        ev.setLocation(xc, yc);
        if (!target.dispatchTouchEvent(ev)) {
        }
        mMotionTarget = null;
        return true;
    }
    if (isUpOrCancel) {
        mMotionTarget = null;
    }
    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        mMotionTarget = null;
    }
    return target.dispatchTouchEvent(ev);
}

此段代码较长,挑重点看,第9行,但是ACTION_DOWN事件时,会进入到第13行的判断,第一个参数disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。而第二个参数!onInterceptTouchEvent(ev)即是否进行拦截的判断,如果我们不复写这个方法,默认为false,就会进入if判断,并获得点击区域有的view,在第29行将手势事件传递给被点击到的view,如果view的dispatchTouchEvent(ev)返回true,表示手势事件被这个view消费了(view的dispatchTouchEvent后续分析),并把该view置给mMotionTarget后返回true。这里,正是因为有view消费了ACTION_DOWN事件,使得当后续的ACTION_MOVE ACTION_UP传递到这个ViewGroup的时候,mMotionTarget不为空,使得47行的target不为空,最后都会执行74行的target.dispatchTouchEvent(ev),即ACTION_DOWN事件被哪个view的onTouch或者onTouchEvent返回了true,那么后续的ACTION_MOVE,ACTION_UP事件将会直接传递给这个view执行。(onTouch和onTouchEvent后续分析)
继续分析这个ViewGroup的dispatchTouchEvent,如果13行的onInterceptTouchEvent被我们复写返回true代表ACTION_DOWN被这个ViewGroup拦截下来,将不会进入if判断,因此mMotionTarget将为null使得target也为null,最后执行了50行的super.dispatchTouchEvent即ViewGroup的父类View的dispatchTouchEvent。并在其中调用onTouch或者onTouchEvent。
如果我们不复写onInterceptTouchEvent让其进入if判断,但如果child的dispatchTouchEvent返回的是false,表示child没有把这个事件消费了,最终也还是调用了50行的super.dispatchTouchEvent(ev)。
现在再来看看上面分析中经常说到的View的dispatchTouchEvent
76 public boolean dispatchTouchEvent(MotionEvent event) {  
77     if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
78             mOnTouchListener.onTouch(this, event)) {  
79         return true;  
80     }  
81     return onTouchEvent(event);  
82 }  
public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}

非常简短的几行代码,是不是比看了ViewGroup的dispatchTouchEvent轻松多了!
首先先判断mOnTouchListener是否为空,这个mOnTouchListener是啥呢,看下源码就知道了
83 public void setOnTouchListener(OnTouchListener l) {  
84     mOnTouchListener = l;  
85 }  
public void setOnTouchListener(OnTouchListener l) {
    mOnTouchListener = l;
}

显然,就是我们设置OnTouchListener的接口实例,并在其中复写了onTouch方法。
if判断的第二个参数用来判断当前view是否是enable为状态。而第三个参数就是我们复写的onTouch方法了,当我们返回了true,这个view的dispatchtouchEvent将返回true,从而使得调用这个dispatchTouchEvent的ViewGroup的dispatchTouchEvent确定了mMotionTarget,确定了消费了ACTION_DOWN事件的view。
而如果我们没有设置OnTouchEvent接口或者onTouch返回false则调用了onTouchEvent。
继续看下view的onTouchEvent
86 public boolean onTouchEvent(MotionEvent event) {  
87     final int viewFlags = mViewFlags;  
88     if ((viewFlags & ENABLED_MASK) == DISABLED) {  
89         // A disabled view that is clickable still consumes the touch   
90         // events, it just doesn't respond to them.   
91         return (((viewFlags & CLICKABLE) == CLICKABLE ||  
92                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
93     }  
94     if (mTouchDelegate != null) {  
95         if (mTouchDelegate.onTouchEvent(event)) {  
96             return true;  
97         }  
98     }  
99     if (((viewFlags & CLICKABLE) == CLICKABLE ||  
100             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
101         switch (event.getAction()) {  
102             case MotionEvent.ACTION_UP:  
103                 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
104                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
105                     // take focus if we don't have it already and we should in   
106                     // touch mode.   
107                     boolean focusTaken = false;  
108                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
109                         focusTaken = requestFocus();  
110                     }  
111                     if (!mHasPerformedLongPress) {  
112                         // This is a tap, so remove the longpress check   
113                         removeLongPressCallback();  
114                         // Only perform take click actions if we were in the pressed state   
115                         if (!focusTaken) {  
116                             // Use a Runnable and post this rather than calling   
117                             // performClick directly. This lets other visual state   
118                             // of the view update before click actions start.   
119                             if (mPerformClick == null) {  
120                                 mPerformClick = new PerformClick();  
121                             }  
122                             if (!post(mPerformClick)) {  
123                                 performClick();  
124                             }  
125                         }  
126                     }  
127                     if (mUnsetPressedState == null) {  
128                         mUnsetPressedState = new UnsetPressedState();  
129                     }  
130                     if (prepressed) {  
131                         mPrivateFlags |= PRESSED;  
132                         refreshDrawableState();  
133                         postDelayed(mUnsetPressedState,  
134                                 ViewConfiguration.getPressedStateDuration());  
135                     } else if (!post(mUnsetPressedState)) {  
136                         // If the post failed, unpress right now   
137                         mUnsetPressedState.run();  
138                     }  
139                     removeTapCallback();  
140                 }  
141                 break;  
142             case MotionEvent.ACTION_DOWN:  
143                 if (mPendingCheckForTap == null) {  
144                     mPendingCheckForTap = new CheckForTap();  
145                 }  
146                 mPrivateFlags |= PREPRESSED;  
147                 mHasPerformedLongPress = false;  
148                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
149                 break;  
150             case MotionEvent.ACTION_CANCEL:  
151                 mPrivateFlags &= ~PRESSED;  
152                 refreshDrawableState();  
153                 removeTapCallback();  
154                 break;  
155             case MotionEvent.ACTION_MOVE:  
156                 final int x = (int) event.getX();  
157                 final int y = (int) event.getY();  
158                 // Be lenient about moving outside of buttons   
159                 int slop = mTouchSlop;  
160                 if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
161                         (y < 0 - slop) || (y >= getHeight() + slop)) {  
162                     // Outside button   
163                     removeTapCallback();  
164                     if ((mPrivateFlags & PRESSED) != 0) {  
165                         // Remove any future long press/tap checks   
166                         removeLongPressCallback();  
167                         // Need to switch from pressed to not pressed   
168                         mPrivateFlags &= ~PRESSED;  
169                         refreshDrawableState();  
170                     }  
171                 }  
172                 break;  
173         }  
174         return true;  
175     }  
176     return false;  
177 }  
public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // 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));
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if (!mHasPerformedLongPress) {
                        // This is a tap, so remove the longpress check
                        removeLongPressCallback();
                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }
                    if (prepressed) {
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                if (mPendingCheckForTap == null) {
                    mPendingCheckForTap = new CheckForTap();
                }
                mPrivateFlags |= PREPRESSED;
                mHasPerformedLongPress = false;
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                break;
            case MotionEvent.ACTION_CANCEL:
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
                removeTapCallback();
                break;
            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();
                // Be lenient about moving outside of buttons
                int slop = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                        (y < 0 - slop) || (y >= getHeight() + slop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();
                        // Need to switch from pressed to not pressed
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
        }
        return true;
    }
    return false;
}

这段代码同样很长,同样挑重点看。
第14行当这个view是可点击的就会进入if判断,否则直接返回false,因此很有可能这个onTouchEvent的代码如果在第14行进入了if判断,很可能里面会有点击事件的执行。我们往下看,第38行执行ACTION_UP手势时,执行的performClick
178 public boolean performClick() {  
179     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
180     if (mOnClickListener != null) {  
181         playSoundEffect(SoundEffectConstants.CLICK);  
182         mOnClickListener.onClick(this);  
183         return true;  
184     }  
185     return false;  
186 }  
public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    if (mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }
    return false;
}
没错,performClick确实执行了mOnClickListener.onClikc(this);就是执行我们设置的onClick事件。并且在onTouchEvent的89行返回了true。
在这部分分析中我们也发现了一个小秘密,如果我们复写了onTouchListener中的onTouch让它返回false,这样dispatchTouchEvent将不会执行到onTouchEvent方法,使得onClick事件永远得不到执行。
好了,全部分析完毕,做几点小总结
1、onTouch优先于onTouchEvent执行,且onTouch返回true将不执行onTouchEvent,并导致onTouchEvent里的点击事件得不到执行
2、手势事件从最外部的ViewGroup的dispatchTouchEvent开始分发,如果onInterceptTouchEvent拦截了该手势事件,即返回true,手势事件将不会往下分发,即不执行view的dispatchTouchEvent,而是让该ViewGroup的的父类dispatchTouchEvent来消费该手势事件。
3、如果View的dispatchTouchEvent消费了ACTION_DOWN事件,即对ACTION_DOWN返回true,后续的的ACTION_MOVE和ACTION_UP将直接被分发到这个view来消费。
分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条