美文网首页
Android View 事件分发机制

Android View 事件分发机制

作者: 大空ts翼 | 来源:发表于2016-08-03 17:57 被阅读136次

    本文参考【张鸿洋的博客】

    1. Android View 事件分发机制 源码解析 (上)

    2. Android ViewGroup事件分发机制

    1. View的事件分发

    View的onTouchEvent:

      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));
        }// 这一块,如果当前view是disable状态且是可点击的则会消费掉事件
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }// 如果设置了mTouchDelegate,则会将事件交给代理者处理,直接return true,如果大家希望自己的View增加它的touch范围,可以尝试使用TouchDelegate
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {// 如果我们的View可以点击或者可以长按,则最终一定return true(即消费掉事件)
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {//判断mPrivateFlags是否包含PREPRESSED,如果包含PRESSED或者PREPRESSED则进入执行体,也就是无论是115ms内或者之后抬起都会进入执行体。
                        // 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();// 这个是把我们的mPrivateFlags中的PRESSED取消,然后刷新背景,把setPress转发下去。
                        }
                        if (prepressed) {// 如果按压的时间不够ViewConfiguration.getTapTimeout()
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());// 延时执行mUnsetPressedState.run()
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();// 立刻执行mUnsetPressedState.run()
                        }// 也就是不管咋样,最后mUnsetPressedState.run()都会执行;
                        removeTapCallback();
                    }
                    break;
                case MotionEvent.ACTION_DOWN:
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;// 给mPrivateFlags设置一个PREPRESSED的标识(这个mPrivateFlags会在ViewConfiguration.getTapTimeout()的延时之后被换为PRESSED)
                    mHasPerformedLongPress = false;// 设置mHasPerformedLongPress=false;表示长按事件还未触发;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());// 发送一个延迟为ViewConfiguration.getTapTimeout()的延迟消息,到达延时时间后会执行CheckForTap里面的run方法
                    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)) {// 判断当然触摸点有没有移出我们的View
                        // Outside button 1、执行removeTapCallback(); 2、然后判断是否包含PRESSED标识,如果包含,移除长按的检查:removeLongPressCallback();3、最后把mPrivateFlags中PRESSED标识去除,刷新背景;
                        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;
      }
    
      private final class CheckForTap implements Runnable {
        public void run() {//取消mPrivateFlags的PREPRESSED,然后设置PRESSED标识,刷新背景,如果View支持长按事件,则再发一个延时消息,检测长按;
            mPrivateFlags &= ~PREPRESSED;
            mPrivateFlags |= PRESSED;
            refreshDrawableState();
            if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                postCheckForLongClick(ViewConfiguration.getTapTimeout());
            }
        }
      }
    
      class CheckForLongPress implements Runnable {
    
        private int mOriginalWindowAttachCount;
    
        public void run() {
            // 1、如果此时设置了长按的回调,则执行长按时的回调,且如果长按的回调返回true;才把mHasPerformedLongPress置为ture;
            // 2、否则,如果没有设置长按回调或者长按回调返回的是false;则mHasPerformedLongPress依然是false;
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick()) {
                    mHasPerformedLongPress = true;
                }
            }
      }
    

    onTouchEvent中的DOWN,MOVE,UP
    DOWN时:
    a、首先设置标志为PREPRESSED,设置mHasPerformedLongPress=false ;然后发出一个115ms后的mPendingCheckForTap;
    b、如果115ms内没有触发UP,则将标志置为PRESSED,清除PREPRESSED标志,同时发出一个延时为500-115ms的,检测长按任务消息;
    c、如果500ms内(从DOWN触发开始算),则会触发LongClickListener:
    此时如果LongClickListener不为null,则会执行回调,同时如果LongClickListener.onClick返回true,才把mHasPerformedLongPress设置为true;否则mHasPerformedLongPress依然为false;
    MOVE时:
    主要就是检测用户是否划出控件,如果划出了:
    115ms内,直接移除mPendingCheckForTap;
    115ms后,则将标志中的PRESSED去除,同时移除长按的检查:removeLongPressCallback();
    UP时:
    a、如果115ms内,触发UP,此时标志为PREPRESSED,则执行UnsetPressedState,setPressed(false);会把setPress转发下去,可以在View中复写dispatchSetPressed方法接收;
    b、如果是115ms-500ms间,即长按还未发生,则首先移除长按检测,执行onClick回调;
    c、如果是500ms以后,那么有两种情况:
    i.设置了onLongClickListener,且onLongClickListener.onClick返回true,则点击事件OnClick事件无法触发;
    ii.没有设置onLongClickListener或者onLongClickListener.onClick返回false,则点击事件OnClick事件依然可以触发;d、最后执行mUnsetPressedState.run(),将setPressed传递下去,然后将PRESSED标识去除;

    整个View的事件转发流程是:

    View.dispatchTouchEvent->View.setOnTouchListener->View.onTouchEvent
    在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。

    2. ViewGroup的事件分发

    1. 基本原理,如下图
      ViewGroup的事件分发

    ps:如果事件分发中有控件消费掉了事件(也就是onTouchEvent返回了true),那么后续的事件中,到达该控件时事件就不会继续分发下去,而是会直接调用onTouchEvent方法,如果该控件是ViewGroup的话,onInterceptTouchEvent也不会执行

    相关文章

      网友评论

          本文标题:Android View 事件分发机制

          本文链接:https://www.haomeiwen.com/subject/osiqsttx.html