本文来自网易云社区

作者:孙有军

我们只看最重要的部分

1: 事件为ACTION_DOWN时,执行了cancelAndClearTouchTargets函数,该函数主要清除上一次点击传递的路径,之后执行了resetTouchState,重置了touch状态,其中执行了 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;就是拦截状态为false,这个与requestDisallowInterceptTouchEvent函数相关。

2: 获取intercepted的值,首先判断了disallowIntercept状态,是否拦截子控件的事件执行。从代码可以看到当disallowIntercept为false时,该状态主要取决于onInterceptTouchEvent函数的返回值,这就是前面我们拦截的函数,如果为true,这时intercepted为true标识拦截。

3: 接着判断了!canceled && !intercepted的值,canceled这里为false,如果intercepted为false,则会进入判断条件,这里假设不拦截,进入后继续判断如果是ACTION_DOWN事件,则会继续进入判断,遍历所有子控件,isTransformedTouchPointInView会判断当前点击区域是否在控件内,如果不在则遍历下一个,之后调用dispatchTransformedTouchEvent函数。最后在调用addTouchTarget函数,将当前选中的控件,挂载到当前点击目标链表。alreadyDispatchedToNewTouchTarget赋值为true。

接着判断mFirstTouchTarget是否为空,经过上一步的addTouchTarget的执行,这里mFirstTouchTarget不为空。第一个事件alreadyDispatchedToNewTouchTarget为true,且target == newTouchTarget,因此handled值为true,如果是后续的事件,则会进入dispatchTransformedTouchEvent中。

我们接着看看第三部中的dispatchTransformedTouchEvent函数:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;     // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    .......
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);                 handled = child.dispatchTouchEvent(event);                 event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }
    .......
    // Done.
    transformedEvent.recycle();
    return handled;
}

这里会进入到第10行,且传递过来的child不为空,因此会继续执行child.dispatchTouchEvent,这里继续执行ViewGroup的dispatchTouchEvent,一直递归执行,直到真正接受点击的控件,到最后child会为空,这里要么是一个View控件,要么是未包含任何子控件的ViewGroup,这时这里会执行View的dispatchTouchEvent。

从上述执行逻辑可以直到,先从DecorView一直递归到Layout,最后再到TextView,这里我们去看看View的dispatchTouchEvent:

public boolean dispatchTouchEvent(MotionEvent event) {

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }     if (onFilterTouchEventForSecurity(event)) {
        //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;
        }
    }     if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }     // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }     return result;
}

首先会停止掉嵌套滑动,之后先判断了ListenerInfo不为空,这里只要是设置了onTouch,onKey,onHover,onDrag等等中的任何一个这里就不为空,具体可以去看看ListenerInfo包含的listenerInfo类型。其次判断了mOnTouchListener不为空,只要设置了onTouchListener这里就不为空,再之后判断了该控件是否是enabled,一般都会enabled,可以代码设置为false,再之后调用了mOnTouchListener的onTouch事件,这里就是外面传进来的onTouchListener,从这里可以看到无论onTouch返回任何值,onTouch事件都会执行,但是如果返回为true,则会导致result为true,!result && onTouchEvent(event)因为短路,不会执行到onTouchEvent事件。

小结

1:onTouch返回为true导致onTouchEvent不能执行
2:如果enable为false,因为短路onTouch不会执行

到此还没有看到任何onClick事件的执行,我们继续去看看onTouchEvent函数:

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();     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) != 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;             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;             case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                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;
        }         return true;
    }     return false;
}

我们首先看ACTION_DOWN事件,这里主要看checkForLongClick,CheckForTap中也调用了该函数,这里就是添加一个长按事件,如果达到长按标准且长按listener不为空,则执行长按事件,接着我们看ACTION_UP,这里看到如果不是长按事件,则调用了performClick,performClick里面执行了onClick事件。

小结

1:onClick事件与onLongClick事件是在onTouchEvent中执行的
2:如果执行了长按事件则onClick不执行
3:就api 23代码,长按的时间间隔为500毫秒

上面解析了intercepted为false的情况,那intercepted为true,它到底是怎么拦截的?

如果intercepted为true,则!canceled && !intercepted为false,不能进入该判断,mFirstTouchTarget为空,会继续执行如下分支:

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

这里第三参数传递的child为null,因此就会执行该控件onTouch与onTouchEvent函数,不会继续递归传递,因此也就拦截了子控件的执行。

总结

  1. 事件接收先从父控件到子控件,如果父控件onInterceptTouchEvent为true,则表示拦截事件。

  2. dispatchTouchEvent的ACTION_DOWN事件中,会清除上一次的点击目标列表,且重置disallowIntercept状态为false,表示拦截,但是真正的拦截状态还是靠onInterceptTouchEvent函数的返回值决定。

  3. 如果为复杂的自定义控件,有滑动事件处理,还需要重写onInterceptTouchEvent。

  4. 如果onLongClick执行,api 23 默认时间为500毫秒,则onClick不执行。

  5. 如果onTouch事件返回为true,则会拦截onTouchEvent事件,onClick,onLongClick事件均不在执行。

网易云免费体验馆,0成本体验20+款云产品!

更多网易研发、产品、运营经验分享请访问网易云社区

相关文章:
【推荐】 在Android中使用FlatBuffers(下篇)
【推荐】 Vue框架核心之数据劫持

Android事件分发机制浅析(3)的更多相关文章

  1. Android事件分发机制浅析(1)

    本文来自网易云社区 作者:孙有军 事件机制是Android中一个比较复杂且重要的知识点,比如你想自定义拦截事件,或者某系组件中嵌套了其他布局,往往会出现这样那样的事件冲突,坑爹啊!!事件主要涵盖onT ...

  2. Android事件分发机制浅析(2)

    本文来自网易云社区 作者:孙有军 上面的两次执行中每次都调用了onInterceptTouchEvent事件,这个到底又是啥?我们去看看他的返回值是什么? public boolean onInter ...

  3. Android进阶——Android事件分发机制之dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

    Android事件分发机制可以说是我们Android工程师面试题中的必考题,弄懂它的原理是我们避不开的任务,所以长痛不如短痛,花点时间干掉他,废话不多说,开车啦 Android事件分发机制的发生在Vi ...

  4. Android事件分发机制(下)

    这篇文章继续讨论Android事件分发机制,首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子 ...

  5. Android事件分发机制(上)

    Android事件分发机制这个问题不止一个人问过我,每次我的回答都显得模拟两可,是因为自己一直对这个没有很好的理解,趁现在比较闲对这个做一点总结 举个例子: 你当前有一个非常简单的项目,只有一个Act ...

  6. android事件分发机制

    android事件分发机制,给控件设置ontouch监听事件,当ontouch返回true时,他就不会走onTouchEvent方法,要想走onTouchEvent方法只需要返回ontouch返回fa ...

  7. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

  8. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  9. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

随机推荐

  1. GreenDao 使用知识小y

    //关于 group by 的实现//--------------------XXXDao.queryBuilder().where(new WhereCondition.StringConditio ...

  2. CSS中box-sizing属性的作用

    今天在项目中看到box-sizing这个属性,以前用过,但是不常用!注意,它是CSS3里的属性喔! W3C 盒子模型:标准盒模型,是指块元素box-sizing属性为content-box的盒模型.一 ...

  3. ArcGIS API for JavaScript开发初探——HelloMap

    1.前言 在开始ArcGIS API for JavaScript开发之前我们需要了解一些基本的知识: 1.开发工具选什么? 前端技术的开发工具选择是一个仁者见仁智者见智的问题,有人喜欢Hbuilde ...

  4. git学习(一)

    提:       远程的主机名(远程仓库服务器名):  origin   本地的主分支: master(本地master分支)      远程的主分支: maste(远程仓库的master分支) gi ...

  5. [转贴] 2016一月12日起.NET 4, 4.5 and 4.5.1 停止安全更新、技术支持 or hotfix

    [转贴] 2016一月12日起.NET 4, 4.5 and 4.5.1 停止安全更新.技术支持 or hotfix https://www.dotblogs.com.tw/mis2000lab/20 ...

  6. javascript模块化---requirejs

    requirejs是异步执行 为什么会出现模块化1.不定什么时候,自己就将全局变量改变了2.函数名的冲突3.依赖关系不好管理如果b.js依赖a.js那么b必须放在a的下面解决的办法1.自执行函数来包装 ...

  7. 【转载】#229 - The Core Principles of Object-Oriented Programming

    As an object-oriented language, c# supports the three core principles of object-oriented programming ...

  8. IOS CALayer基本使用 (图层)

    ● 其实UIView之所以能显示在屏幕上,完全是因为它内部的一个图层(CALayer) ● 在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView 的l ...

  9. 【BZOJ4540】 [HNOI2016] 序列(莫队)

    点此看题面 大致题意: 求出一个序列的一段区间中所有子序列最小值之和. 莫队 这道题其实是一道莫队题. 但是需要大量的预处理. 预处理 先考虑预处理两个数组\(lst_i\)和\(nxt_i\),分别 ...

  10. SSM框架快速搭建

    1.   新建Maven项目 ssm 2.    pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xml ...