View 事件处理

让我自己看源码是看不懂的,还好有这么多大神。


1. dispatchTouchEvent

ViewGroup 在分发事件时会调用子 View 的 dispatchTouchEvent 方法。

对于 View (非 ViewGroup)来说,dispatchTouchEvent 方法被理解为 "handleEvent"  可能会更会合适。

该方法的返回值表示 Event 是否被 View 消费了,true 表示消费了。

public class MyView extends View {

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("MyView", "dispatchTouchEvent start" );
        boolean result = super.dispatchTouchEvent(event);
        Log.e("MyView", "dispatchTouchEvent end " + result);
        return result;
    }
    
}

点击后日志如下:

03-25 10:48:00.296 18114-18114/com.unicorn.viewtry E/MyView: dispatchTouchEvent start
03-25 10:48:00.296 18114-18114/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false

基本 View 并没有消费掉 Event。


2. dispatchTouchEvent 源码

来看看 View 是如何 handle event 的,重点的部分可能就这么点,源码是 6.0 版本的。

        boolean result = false;
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
        return result;


3. OnTouchListener

添加 OnTouch 事件
  findViewById(R.id.myView).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("MyView", "onTouchListener onTouch start");
                Log.e("MyView", "onTouchListener onTouch end " + false); // 不消费事件
                return false;
            }
        });
点击后日志如下:
03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: dispatchTouchEvent start
03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: onTouchListener onTouch start
03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: onTouchListener onTouch end false
03-25 11:46:56.121 31150-31150/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false
如果

        findViewById(R.id.myView).setEnabled(false);
那么 OnTouch 事件不会被触发。


4. onTouchEvent

如果 OnTouchListener 没消费掉事件,那么就轮到 onTouchEvent 了。
这个函数比较复杂,一点点看。
  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);
        }
注释上说,disabled 的 View 依旧会消费事件,即使它什么也没做。
只要 View 是 clickable 或者 long_clickable 的。
    if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
假如设置了 TouchDelegate ,那么由它来负责。
 if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {

            // 之后分析
            return true;
        }

        return false;
如果 View 是 clickable 或者 long_clickable 的,则消费事件。

点击日志如下:
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: dispatchTouchEvent start
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchListener onTouch start
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchListener onTouch end false
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchEvent start
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: onTouchEvent end false
03-25 12:24:17.964 20423-20423/com.unicorn.viewtry E/MyView: dispatchTouchEvent end false

默认的 View 是不可点击的,所以事件还没被消费。

5. OnLongClickListener

  case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // 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();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;
看 checkForLongClick 函数:
 private void checkForLongClick(int delayOffset) {
        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
            mHasPerformedLongPress = false;

            if (mPendingCheckForLongPress == null) {
                mPendingCheckForLongPress = new CheckForLongPress();
            }
            mPendingCheckForLongPress.rememberWindowAttachCount();
            postDelayed(mPendingCheckForLongPress,
                    ViewConfiguration.getLongPressTimeout() - delayOffset);
        }
    }
这里 post 了一个 500 毫秒的延时事件。
 private final class CheckForLongPress implements Runnable {
        private int mOriginalWindowAttachCount;

        @Override
        public void run() {
            if (isPressed() && (mParent != null)
                    && mOriginalWindowAttachCount == mWindowAttachCount) {
                if (performLongClick()) {
                    mHasPerformedLongPress = true;
                }
            }
        }

        public void rememberWindowAttachCount() {
            mOriginalWindowAttachCount = mWindowAttachCount;
        }
    }
如果 500 毫秒后 View 依旧是 pressed 状态,则执行 performLongClick 函数,并将返回结果保存在 mHasPerformedLongPress 中。
 public boolean performLongClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLongClickListener != null) {
            handled = li.mOnLongClickListener.onLongClick(View.this);
        }
        if (!handled) {
            handled = showContextMenu();
        }
        if (handled) {
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
        }
        return handled;
    }
在 performLongClick 函数中 OnLongClick 事件被触发。

长按日志如下:
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent start
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch start
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch end false
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchEvent start
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: onTouchEvent end true
03-25 12:35:54.199 25735-25735/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent end true
03-25 12:35:54.700 25735-25735/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick start
03-25 12:35:54.700 25735-25735/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick end false

长按事件触发距离事件分发结束相差约 500 毫秒。
因为设置了 OnLongClickListener,View 成为 long_clickable 的,所以 onTouchEvent 消费了事件。
onLongClick 的返回值只和是否触发 OnClickListener 有关。

6. OnClickListener

  case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_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 (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 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) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;
如果 OnLongClickListener 被执行且返回 true,那么 perfermClick 函数不会执行。
public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

长按日志如下:

03-25 13:25:22.730 15131-15131/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent start
03-25 13:25:22.730 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch start
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchListener onTouch end false
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchEvent start
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: onTouchEvent end true
03-25 13:25:22.731 15131-15131/com.unicorn.viewtry E/MyView 0: dispatchTouchEvent end true
03-25 13:25:23.230 15131-15131/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick start
03-25 13:25:23.230 15131-15131/com.unicorn.viewtry E/MyView: onLongClickListener onLongClick end false
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: dispatchTouchEvent start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchListener onTouch start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchListener onTouch end false
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchEvent start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: onTouchEvent end true
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView 1: dispatchTouchEvent end true
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView: onClickListener onClick start
03-25 13:25:23.696 15131-15131/com.unicorn.viewtry E/MyView: onClickListener onClick end false


这里有个小细节,当 Down event(0 和 1 分别代表 Down 和 Up) 被 View 消费后。

View 才会收到对应的 Up event,这就涉及到 ViewGroup 是如何分发事件了。


推荐:

http://blog.csdn.net/dmk877/article/details/48781845


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值