Android View 事件分发机制

2016-08-03  本文已影响136人  大空ts翼

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

  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也不会执行

上一篇下一篇

猜你喜欢

热点阅读