Android布局中同级View的事件传递优先级

个人原创,转载请注明出处:http://08643.cn/p/10a2d2304f1e

说起Android中View的事件分发机制,不少开发者脑海中应该会立刻浮现出一幅流程图。已经有许多文章详细的分析了点击事件在上下级ViewViewGroup之间的传递规则。但同级View之间的点击事件是如何专递的呢?换句话说,处于同一个ViewGroup内的两个View重合时,ViewGroup是如何决定传递到哪一个View的?部分有经验的开发者可能会说:按照xml中的排列顺序,最后的优先触发。的确,在相当长的时间里我也是这么认为的。但在最近的开发中我遇到了一个比较棘手的问题,这也促使我从源码中去进行更深入的探索。

决定事件传递对象的源码分析

点击事件的分发机制主要由dispatchTouchEvent(),onInterceptTouchEvent()onTouchEvent()三个方法来完成,其中后两个方法都是在第一个方法中调用的,作用分别是拦截事件和处理事件,与本文关系不大。那么,决定父控件将点击事件传递给哪个子控件的逻辑,就应该在dispatchTouchEvent()剩余的代码里。通常dispatchTouchEvent()这个方法不太可能会被重写,因此我们直接看ViewGroupdispatchTouchEvent()方法:

...
    final int childrenCount = mChildrenCount;
    if (newTouchTarget == null && childrenCount != 0) {
        final float x = ev.getX(actionIndex);
        final float y = ev.getY(actionIndex);
        // Find a child that can receive the event.
        // Scan children from front to back.
        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
        final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
        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);
            ...
        }
    }
...

整段方法的代码好长啊,足足有200+行!不过我们要善于抓住核心。在方法体内搜索child关键字能定位到上图所示的一段代码。通过行间的注释我们可以得知判断父控件将点击事件传递给哪个子控件的逻辑就在这段代码中。而决定这个子控件的实例的代码应该就是最后一行:

final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);

很显然getAndVerifyPreorderedView()这个方法决定了最终由哪个子控件来接收点击事件。方法的具体逻辑我们先放到一边,先来看看该方法接收的3个变量,其中children根据上方代码可推测出是包含了所有(重合)子控件的实例数组,而另外两个变量preorderedListchildIndex从变量名就能猜到和顺序有关。我们先来看看决定preorderedListbuildTouchDispatchChildList()方法,该方法直接调用了buildOrderedChildList()方法,我们继续看该方法的代码:

ArrayList<View> buildOrderedChildList() {
    final int childrenCount = mChildrenCount;
    if (childrenCount <= 1 || !hasChildWithZ()) return null;

    if (mPreSortedChildren == null) {
        mPreSortedChildren = new ArrayList<>(childrenCount);
    } else {
        // callers should clear, so clear shouldn't be necessary, but for safety...
        mPreSortedChildren.clear();
        mPreSortedChildren.ensureCapacity(childrenCount);
    }

    final boolean customOrder = isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {
        // add next child (in child order) to end of list
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View nextChild = mChildren[childIndex];
        final float currentZ = nextChild.getZ();

        // insert ahead of any Views with greater Z
        int insertIndex = i;
        while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
            insertIndex--;
        }
        mPreSortedChildren.add(insertIndex, nextChild);
    }
    return mPreSortedChildren;
}

又是好长的一段代码哟!不过眼尖的应该很快就能注意到第二行

if (childrenCount <= 1 || !hasChildWithZ()) return null;

这个判断语句,childrenCount很明显就是子控件的数量,如果小于等于1就不用判断了。而hasChildWithZ()熟悉布局文件的开发者应该能猜到这是查看是否有child设置了Z轴相关属性,取反意味着如果没有child设置Z轴就返回null。其实搞清楚这里基本上下面的一大段代码就不用看了!根据实际经验很容易得出这段代码就是让Z轴越大的优先级越高!

接着再来看childIndex,进入方法getAndVerifyPreorderedIndex()中:

private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
    final int childIndex;
    if (customOrder) {
        final int childIndex1 = getChildDrawingOrder(childrenCount, i);
        if (childIndex1 >= childrenCount) {
            throw new IndexOutOfBoundsException("getChildDrawingOrder() "
                    + "returned invalid index " + childIndex1
                    + " (child count is " + childrenCount + ")");
        }
        childIndex = childIndex1;
    } else {
        childIndex = i;
    }
    return childIndex;
}

这段代码的逻辑比较简单,变量customOrder顾名思义就是自定义顺序,如果为false就是childIndex取默认顺序,而默认顺序一般来讲就是xml中子控件的定义顺序了。其实看到这里整个判断的逻辑已经比较清晰明了了,主要的影响因素就是Z轴大小xml中的定义顺序。

最后我们再回过头来看给child最终赋值的getAndVerifyPreorderedView()方法:

private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
        int childIndex) {
    final View child;
    if (preorderedList != null) {
        child = preorderedList.get(childIndex);
        if (child == null) {
            throw new RuntimeException("Invalid preorderedList contained null child at index "
                    + childIndex);
        }
    } else {
        child = children[childIndex];
    }
    return child;
}

这时再看这段代码就很明显了,preorderedList不为null时优先看preorderedList,否则直接看childIndex,即设置了Z轴就Z轴大的优先,否则就是xml定义靠后的优先。到这里,决定父控件将点击事件传递给哪个子控件的逻辑已基本清晰。

事件在子控件间的传递

一般来说不用考虑这个问题,因为ViewonTouchEvent()默认会消耗事件,除非它是不可点击的,即ViewclickablelongClickable属性都为false。当优先级最高的子View不可点击时,事件会传递到次高的View上,以此类推。

总结

当父布局下有两个重合的子控件A和B时,点击事件的传递遵循:

  1. 如果子控件设置了Z轴(elevationtranslationZ),就Z轴大的优先。
  2. 如果没有设置Z轴或Z轴相同,则xml定义靠后的优先。
  3. 当优先级最高的子控件为不可点击(clickablelongClickable属性都为false)时,事件会传递到优先级次高的控件上,否则会默认消耗掉事件。
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351