美文网首页
View触摸事件源码分析2

View触摸事件源码分析2

作者: 陆元伟 | 来源:发表于2019-07-31 16:26 被阅读0次

    View触摸事件源码分析

    View的dispatchTouchEvent方法的省略版源码

    public boolean dispatchTouchEvent(MotionEvent event) {
        //dispatchTouchEvent的返回值
        boolean result = false;
        //...balabala省略前面部分代码
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
        //判断是否有onTouchListener监听,如果有,执行listener的
        //onTouch方法,
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
        //如果onTouch方法返回false,继续执行onTouchEvent方法
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        return result;
    }
    

    通过上面的代码可以得出以下结论:
    1 如果ViewonTouchListener,则onTouch方法优于onTouchEvent方法先执行
    2 如果 ViewonTouchListeneronTouch方法返回了true,则onTouchEvent方法不会执行
    3 如果ViewonTouchListener,并且onTouch方法返回了true,dispatchTouchEvent方法返回true。如果onTouch返回了false或者没有onToucheListener,则dispatchTouchEvent方法和onTouchEvent的返回值一致。

    ViewGroup dispatchTouchEvent方法的省略版源码

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //dispatchTouchEvent方法的返回值
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            // Check for interception.是否拦截标志
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //调用拦截方法,该方法默认返回false
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was chang
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
            //如果没有拦截
            if (!intercepted) {
                 final View[] children = mChildren;
                 for (int i = childrenCount - 1; i >= 0; i--) {
                      final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                       final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);  
                       if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                  
                        }
                   }
               }
    
            }
        
           return handled;
    }
    

    dispatchTransformedTouchEvent方法

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        final int oldAction = event.getAction();   
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
     
        if (child == null) {
            //调用View的dispatchTouchEvent方法,View的dispatchTouchEvent方法如果执行则看前面分析View的dispatchTouchEvent方法源码
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            //调用子view的dispatchTouchEvent方法  
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        // Done.
        transformedEvent.recycle();
        return handled;
    }
    

    ViewGroupdispatchTouchEvent方法比较复杂,主要是它是个控件容器,可能有很多子控件,要考虑事件如何传递,传递给哪个子控件,该不该传递给子控件等等多种情况。
    结论:
    1 ViewGroup里面新增了一个拦截方法onInterceptTouchEvent,控制该事件要不要传递给它的子控件。
    2 若onInterceptTouchEvent方法返回true,则子控件接收不到事件即dispatchTouchEvent方法不会调用,而会调用父类View 的dispatchTouchEvent方法。若返回false,则会子控件的dispatchTouchEvent方法
    现测试检验查看

    图片1.png

    MyView继承View,里面添加onTouchListener监听.MyViewGroup 继承ViewGroup.在相应的方法里面添加打印语句,返回值全都默认值,经上面分析可知道的,猜想得知执行顺序MyViewGroup.dispatchTouchEvent->MyViewGroup.onInterceptTouchEvent->MyView.dispatchTouchEvent->MyView.onTouch->MyView.onTouchEvent
    打印结果如下图,

    图片2.png

    符合猜想;
    1 小结:
    View和ViewGroup的dispatchTouchEvent默认返回都为false;
    ViewGroup默认onInterceptTouchEvent返回也为false;
    action为0表示为当前事件为ACTION_DOWN事件;

    but,MOVE和UP事件跑哪去了....慢慢往下看;

    其他都是默认值 现在改变View的监听onTouch返回值为true;
    这时候View的onTouchEvent应该就得不到执行了;
    打印结果如下图,

    图片3.png

    发现打印日志就多了很多,action为2表示ACTION_MOVE事件,等于1表示ACTION_UP事件;
    2小结:
    经上面分享源码得知,此时MyViewdispatchTouchEvent方法返回的也是true,由日志也可看出;
    由打印日志可以看出,MyViewGroupdispatchTouchEvent方法也返回了true;
    可以看出MyViewGroupdispatchTouchEvent方法返回了true,才有了后续的ACTION_MOVEACTION_UP事件;
    注意看,此时MyViewGrouponTouchEvent方法也不会执行了....Why...继续往下慢慢分析

    其他都是默认值 改变ViewdispatchTouchEvent方法返回true;

    图片4.png

    这个和上面那个日志的区别就是会执行View的onTouchEvent方法,其他的都是一样的;
    打印如下;

    图片5.png

    其他都是默认值,View的onTouchEvent返回true;

    图片6.png

    日志和上面的差不多,差别就是前面View的onTouchEvent返回false,这里都是true;
    没有设置dispatchTouchEvent的返回值,但是它返回的是true;
    前面分析过了,这种情况dispatchTouchEvent的返回值和onTouchEvent是一样的;
    依然ViewGrouponTouchEvent方法没有执行;

    图片7.png

    3小结
    通过小结1 和后面的对比发现,如果View的dispatchTouchEvent返回值为true,则ViewGrouponTouchEvent就不会执行了;
    而想要ViewdispatchTouchEvent返回值为true,除了复写该方法在dispatchTouchEvent里面返回true之外,在则可以设置一个监听onTouchListener返回true,或者在onTouchEvent里面返回true;

    平常我们用的最多的是复写onTouchEvent方法;

    再看View onTouchEvent方法源码;

    public boolean onTouchEvent(MotionEvent event) {
            //前面省略balabala代码....
        final int action = event.getAction();
            //还可以设置touch代理  
        if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {         
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                //调用onClickListener回调
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                     break;
                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
                    if (isInScrollingContainer) {
                        //检测是否长按
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        //检测是否长按
                        checkForLongClick(0, x, y);
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);
                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();
                            setPressed(false);
                        }
                    }
                    break;
            }
            //这里直接返回true了
            return true;
        }
    
        return false;
    }
    

    初看一下既然返回false了,那么switch里面应该就不会走了;
    突发奇想,既然switch都不会走了,难道onClickListener也不会走了???
    MyViewMyViewGroup所有返回值都默认,添加如下代码,设置了onClick监听

    图片8.png

    结果Toast神奇的弹出来了!!!
    和上面预期的不一样啊,既然switch都不走了,那为啥onClick方法会被调用?
    打印如下;

    图片9.png 图片10.png

    ViewonTouchEvent,dispatchTouchEvent返回值为true;
    ViewGroupdispatchTouchEvent返回为true了。

    图片11.png

    就是一个加setOnclickListener和不加的区别;
    点进去这个方法看;

    图片12.png

    前面有个setClickable顿时恍然大悟,如梦出醒;
    再看onTouchEvent方法

    图片13.png

    也就是默认这个值是false,如果这个值是false,那onTouchEvent方法也就直接返回false.这也是导致了dispatchTouchEvent方法返回了false;
    google工程师用一个标志viewFlags判断当前是否可点击或者长按或者是contextClickable;
    点击和长按是在onTouchEvent里面处理的,这个onContextClickListeneronTouchEvent里面没看到被调用,用的也比较少,暂时略过,以后发现了用处再来补充
    4 小结
    在不设置setClickable,或者setLongClickable情况下,onTouchEvent返回false;
    若设置setClickable,或者setLongClickabletrue,则onTouchEvent也返回true;
    可以通过代码设置,也可以在布局文件里面直接设置ClickableLongClickable;

    继续测试,所有值默认,ViewGroupdispatchTouchEvent返回true

    图片14.png

    日志打印如下图

    图片15.png

    5 小结
    由于ViewonTouchEvent返回了false,所以ViewGrouponTouchEvent才会被调用;
    由于ViewonTouchEventACTION_DOWN就返回了false,所以后续的ACTION_MOVEACTION_UP也就没有接收到;
    即时ViewGrouponTouchEventACTION_DOWN返回了true,他的后续MOVEUP也能接收到;

    现改成如下方式;

    图片16.png

    输出日志如下:

    图片17.png

    6 小结
    ViewGroupdispatchTouchEvent方法里面只要ACTION_DOWN返回了true,其他的MOVE和UP事件不受影响

    继续测试,onInterceptTouchEven返回true,代码如下

    图片18.png

    由前面分析ViewGroup源码得知,此时View接收不到任何事件的;
    输入日志如下

    图片19.png

    View里面onTouchEvent返回true

    图片20.png

    ViewGroup onInterceptTouchEventMOVE事件里面返回true

    图片21.png

    打印日志如下


    图片22.png
    图片23.png

    这个时候View onTouchEvent方法只接收到了DOWNCANCEL事件;
    ViewGroup 接收到了MOVEUP事件
    小结:
    ViewGroup若在MOVE方法返回true,子控件只能接收到DOWNCANCEL事件。ViewGroup可以接收到MOVEUP事件
    现在在ViewGroupUP事件上拦截

    图片24.png

    输出日志如下


    图片25.png 图片26.png

    View接收到onTouchEvent 方法接收到DOWNMOVE和CANCEL事件;
    ViewGroupdispatchTouEvent里面可以接收DOWN,MOVE,和UP事件,onTouchEvent没有执行;
    总结
    1 如果一个控件能够接受到事件则最新执行的方法肯定是dispatchTouEvent方法;
    2 如果一个控件设置了onTouchLister,则onTouchListener优于onTouchEvent方法执行;
    3 有onTouchListener的情况下,如果onTouchListener返回true,则dispatchTouEvent的返回值也为true,并且onTouchEvent方法不会再执行;
    4 没onTouchListener或者onTouchListener返回false的情况下,dispatchTouEvent的返回值和onTouchEvent是一样的; 5ACTION_DOWN,ACTION_MOVE,ACTION_UP事件是一系列连续的,如果某个事件返回了false,则后续事件也不再调用 6 父容器控件的onInterceptTouchEvent方法也是如此,如果某个事件返回了true,则子控件会接收到ACTION_CANCEL事件,并且子控件后续事件也不在会执行。 7 字控件的dispatchTouEvent方法如果能够执行,并且返回true.则父容器控件的onTouchEvent`方法就不会执行。

    参考文章http://www.bubuko.com/infodetail-1466509.html

    相关文章

      网友评论

          本文标题:View触摸事件源码分析2

          本文链接:https://www.haomeiwen.com/subject/zyytdctx.html