Android事件分发机制详解

事件分发机制有多重要就不多说了,处理滑动冲突时,这是必备的基础知识!写下这篇文章的目的是确定自己是否真的理解,有助于加深印象以及了解自身理解状况吧,算是一份读了几篇文章之后的读后感。(ps:2019-4-30距离第一次写也才20多天的时间,回想事件分发的细节竟然一时想不起来,回来看到这个,顿时感觉写文章好有意义!而且自己写的文章,很符合自己的口味,读起来很舒服呀~一气呵成!^_^)

分发对象

分发的对象是touch事件,touch事件有4种:

MotionEvent.ACTION_DOWN          按下View(所有事件的开始)

MotionEvent.ACTION_UP                 抬起View(与DOWN对应)

MotionEvent.ACTION_MOVE           滑动View

MotionEvent.ACTION_CANCEL     结束事件(非人为原因)

 

分发流程

一个touch事件分发传递的流程是:activity->window->viewgroup->view   记!记!记!

 

主要方法

方法只有三个:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()。

 

viewGroup的dispatchTouchEvent源码分析

老实说现在源码看的一脸懵逼,复制份大佬的伪代码讲解:

这三个方法的联系可以用下面的伪代码来表示:

点击事件产生后,会直接调用dispatchTouchEvent()方法

public boolean dispatchTouchEvent(MotionEvent ev) {
    //代表是否消耗事件
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
    //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
    //则该点击事件则会交给当前View进行处理
    //即调用onTouchEvent ()方法去处理点击事件
      consume = onTouchEvent (ev) ;
    } else {
      //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
      //则该点击事件则会继续传递给它的子元素
      //子元素的dispatchTouchEvent()就会被调用,重复上述过程
      //直到点击事件被最终处理为止
      consume = child.dispatchTouchEvent (ev) ;
    }
    return consume;
   }

总结整理下:

先判断是否拦截,拦截了就去调用viewgroup自己的onTouchEvent事件,并将返回值作为本次dispatchTouchEvent的返回值。

没拦截就去找自己的子view,把子view的dispatchTouchEvent的返回值作为自己的返回值。

view的dispatchTouchEvent源码分析

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

总结整理:

子view的dispatchTouchEvent事件先来3个判断:

1.touchListener是否不为空

2.该view是否可用

3.touchListener.onTouch的返回值为true?

这三个都成立,则子view返回true。

如果有一个不成立,则执行onTouchEvent,并将该返回的返回值作为子View的dispatchTouchEvent的返回值。

 

touch和onclick的优先级对比

结合上面view的dispatchTouchEvent代码分析,可以发现,我们是先去判断TouchListener.touch的返回值,即我们先执行了该方法,返回true。如果没有执行onTouchListener,那么子view的dispatchTouchEvent会执行onTouchEvent方法,在该方法中,我们会执行performClick方法。

看下代码~:

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;
            }
        }
        //如果该控件是可以点击的就会进入到下两行的switch判断中去;

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。

            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    // 在经过种种判断之后,会执行到关注点1的performClick()方法。
                    //请往下看关注点1
                    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)) {
                                    //关注点1
                                    //请往下看performClick()的源码分析
                                    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;
            }
            //如果该控件是可以点击的,就一定会返回true
            return true;
        }
        //如果该控件是可以点击的,就一定会返回false
        return false;
    }

可以发现在ACTION_UP中,调用了performClick()方法,

在该源码中,如下图,可以看到先判断onCLickListener是否为空,不为空就调用onClick方法。

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的dispatchTouchEvent中,先调用了touch方法,该方法返回true,并且控件可用,则dispatchTouchEvent直接返回true,不执行onTouchEvent方法(在该方法中才调用到OnClick),即如果touch方法返回true就屏蔽了onclick方法,touch方法的优先级是高于onclick方法的。


 

参考:https://blog.csdn.net/Simon_Crystin/article/details/78656113

              https://www.jianshu.com/p/38015afcdb58

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值