android View点击事件分发流程

android View事件分发流程

android 中的view虽然不是四大组件,但是同样也是相当重要的。不论是我们在平时自定义控件还是面试的时候总会遇到一些关于view点击事件分发的一些问题。接下来就让我给大家分享一下关于view的事件分发流程。

要想了解view的事件分发首先我们要知道什么是事件分发?所谓点击事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统需要把这个事件传递到具体的View,而这个传递的过程就是事件分发的过程。点击事件的分发过程有三个很重要的方法共同完成:dispatchTouchEvent、onInterceptTouchEvent、和onTouchEvent,下面我们先介绍一些这几个方法:

public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
在上述方法内部调用,用来判断是佛偶拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列当中,此方法不会再次被调用了,返回结果表示是否拦截当前事件,true表示拦截。
public boolean onTouchEvent(MotionEvent ev)
在dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消费当前时间,如果不消费,在同一个时间序列当中,当前View无法再次接收到事件。

上述的三个方法到底有什么关系呢?我们可以用下面的伪代码来描述他们之间的关系。

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInTerceptTouchEvent(ev)){
        consume= ontouchEvent(ev);
    }else{
        consume=child.dispatchTouchEvent(ev);       
    }
    return consume;

}

这一段伪代码可以说是吧他们之间的关系表达的淋漓尽致。

由于ViewGroup的事件分发流程稍微有点复杂我们放到下一篇文章单独介绍,下一篇我们主要介绍一下点击事件是怎么从顶级View一步一步下发的。
接下来我们就共同来分析一下View点击事件的下发。这里的View不包含ViewGroup。接下来让我们先看一下View的dispatchTouchEvent方法,如下所示

public boolean dispatchTouchEvent(MotionEvent event) {
     boolean result = false;
       ...
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        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;
}

View对点击事件的处理过程就比较简单了,因为View(不包含ViewGroup)是一个单独的元素,它没有子元素因此无法向下传递事件,所以只能自己处理事件。
从上面的源码可以看出View对点击事件的处理过程,首先会判断view有没有甚至OnTouchListener事件,若设置了ontouch事件并且onTouch方法的返回值为true,那么dispatchTouchEvent直接返回true不会调用View的onTouchEvent方法,可见onTouchListener的优先级高于onTouchEvent,这样的好处是外界可以很方便的处理点击事件。
接下来在分析一下onTouchEvent的实现。首先让我们看一下view不可用状态下点击事件的处理。

 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }

从这段代码可以看出view只要clickable是true那么他就是可点击的。无论这个view可不可用都是会消费这个点击事件的。

接着往下看,如果view设置了代理,那么他就会直接调用代理的onTouchEvent的方法,里面的处理流程与view的差不多。

if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

接下来我们看一下view的onTouchEvent对点击事件的具体处理,如下所示:

 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    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) {
                            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) {

                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                    }

                   ...

                    }
                    break;
            }

            return true;
        }

        return false;

从上面的代码我们可以看出只要CLICKABLE与LONG_CLICKABLE有一个为true那么这个view就会消费掉这个事件,即onTouchEvent的返回值为true。即使这个view是DISABLE的也会消费掉这个事件,上面我们已经分析过了。然后就是当MotionEvent.ACTION_UP事件触发时就会触发performClick()方法,在这个方方法中如果view 设置了 onClickListener那么就会调用onClick方法,也就是我们常常给view设置点击事件的回调方法

    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);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

View的LONG_CLICKABLE属性默认是false,而View的CLICKABLE是true还是false就不一定了,主要看这个view是否是可点击的,如果是可点击的那么CLICKEABLE自然就是true,反之如果不可点击那么CLICKABLE就是false。注意,在我们设置点击事件监听的时候会自动的把CLICKABLE置成true,长按点击事件也是如此,从源码我们就可以看出 如下:

 public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }


public void setOnLongClickListener(@Nullable OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        getListenerInfo().mOnLongClickListener = l;
    }

到这里我们对View的点击事件的分发机制就分析完毕了。如果有不对的地方希望大家多多指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值