Android事件分发机制之View的事件分发

我们在开发过程中经常会用到控件的 setOnTouchListener,setOnClickListener来进行点击、触摸、滑动、长按等事件的处理。那么从手指点到屏幕,到最终这个触摸事件是怎么被认定为点击,或者长按等操作的识别呢,下面通过源码的方式来看一下。
总体我们分两篇文章来讲分别为View的事件分发,ViewGroup的事件分发。我们先来看View的事件分发。

ViewGroup事件分发机制
Android事件分发机制之ViewGroup事件分发

当我们点击任何一个view的时候,最终都会调用该view的dispatchTouchEvent方法,比如点击一个button但是他的源码中没有该方法,那就去他的父类去找,最终在view类中找到该方法。在较新的的sdk中dispatchTouchEvent比较复杂,我们以sdk 22版本的方法来看,因为事件分发机制的主体逻辑都是一样,老版本的代码更容易理解。新版本上加上了一些对屏幕的判断和事件丢弃功能。下面看代码

public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}

这段代码中我们可以看这个if中包含三个条件 三个条件都为true的话就会直接返回true从而下面的onTouchEvent方法不会执行。我们挨个看一下
mOnTouchListener 也就是我们给该控件有没有设置OnTouchListener,这个很好理解
(mViewFlags & ENABLED_MASK) == ENABLED 就是这个view的状态是不是可用,一般默认是可用的。
mOnTouchListener.onTouch(this, event) 这个就是调用了 我们给view设置的mOnTouchListener的onTouch方法,如果这个方法返回true 那么整个条件就都是true,直接返回不会执行onTouchEvent方法。如果返回了false那么就会调用下面的 onTouchEvent方法。
我们看一下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));
    }
    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;
}
 

我们手指在点下后首先会出发 ACTION_DOWN 事件,这里会发送一个500ms的延迟事件,这个事件如果被执行的话就会调用我们的mOnLongClickListener.onLongClick方法。但是我们不是所有的操作都会触发这个onLongClick的,
我们来看一下ACTION_UP事件,这里有一个mHasPerformedLongPress 标示标示着这个在ACTION_DOWN 的时候埋下的延迟长点击事件是否执行了,如果为false,则说明现在还没到500ms但是手指已经离开屏幕了 所以这是一个tap事件,则把该延迟事件移除,后面代码会执行到performClick方法,该方法中最终会调用到view的onClick方法。如果为true 则说明长按事件已经执行了 就不会走view的click事件了
这也是 点击跟长按的实现方式。
我们重新屡一下View的事件分发流程
dispatchTouchEvent—>ontouchListener------>onTouchEvent----->onClickListener/onLongClickListener
流程中,如果onTouchListener返回true的话,也就是说这个事件被onTouchLitener消费掉了,后面的onTouchEvent就不会被执行了。

总结

在屡这个事件分发机制的时候 首先我们先只看view的分发,先抛开viewgroup的事件拦截等机制。一定要看源码!一定要看源码!一定要看源码!
下一篇文章,ViewGroup事件分发机制
Android事件分发机制之ViewGroup事件分发

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值