查看: 1856|回复: 0

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

[复制链接]
  • TA的每日心情

    2014-4-10 13:56
  • 签到天数: 5 天

    连续签到: 1 天

    [LV.2]偶尔看看I

    发表于 2014-1-24 08:40:57 | 显示全部楼层 |阅读模式
    分享到:
    本帖最后由 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 下一条



    手机版|小黑屋|与非网

    GMT+8, 2024-5-17 00:23 , Processed in 0.114337 second(s), 15 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.