前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再论Android的事件分发机制

再论Android的事件分发机制

作者头像
提莫队长
发布2020-06-02 15:27:40
6890
发布2020-06-02 15:27:40
举报
文章被收录于专栏:刘晓杰刘晓杰

Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。

View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。

ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。

接下来我以ViewGroup的三个方法为例

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            // 是否需要拦截.需要就调用onInterceptTouchEvent(flag标记位决定)
            // 注意FLAG_DISALLOW_INTERCEPT这个flag,可以通过requestDisallowInterceptTouchEvent来设置
            // 当标志位设置之后ViewGroup将无法拦截除了ACTION_DOWN以外的事件了
            // mFirstTouchTarget 是什么?后面的代码表示,当ViewGroup的点击事件被子View消耗,那mFirstTouchTarget就会指向该子View
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action);
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            // 是否被取消
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            if (!canceled && !intercepted) {
                // 如果没有取消也没有拦截,那就让子view处理

                    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);
                            ......
                            会调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
                        }
                    }
            }

            // 子view未处理
            // 可能有两种情况:一、ViewGroup下面没有子View。二、子View没有消耗点击事件。
            if (mFirstTouchTarget == null) {
                // child=null,永远调用super.dispatchTouchEvent
                // handled = super.dispatchTouchEvent
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                        // alreadyDispatchedToNewTouchTarget在循环遍历的时候如果找到,会置为true
                        // handled = true表示被子view消费掉
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                            // handled = true表示被子view消费掉
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
        }

        return handled;
    }

注意其中的一个很重要的参数mFirstTouchTarget。这是个链表 (找到子view,并且消费才会添加到链表中) 再来看一下dispatchTransformedTouchEvent

    // 子ciew存在就让子view消费,不存在super.dispatchTouchEvent(event)
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // 循环遍历子view时,cancel=false.当child不存在时交给父view,否则交给子view
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ......
        return handled;
    }

总结

事件向下传递,假设被拦截(一般不会被拦截,设置了FLAG_DISALLOW_INTERCEPT表示可以拦截),此时mFirstTouchTarget == null 调用自己的onInterceptTouchEvent,处理完了return super.dispatchTouchEvent 往上抛 现在来看一般情况,没被拦截,大致情况如下图

当找到mFirstTouchTarget时.png

会一直往下找,如果找到mFirstTouchTarget,也就是上图最底下的View,那么就会在View的dispatchTouchEvent返回true表示消费。如果没有找到,说明要么没有子view,要么没有消费,都往上抛,也就是super.dispatchTouchEvent

扩展:

还记得ACTION_CANCEL么?假如点击了以后滑动到View的外面,响应了ACTION_CANCEL了呢?看dispatchTouchEvent函数,那么很明显canceled=true,也不用遍历子view,直接返回mFirstTouchTarget=null,但此时中间的ViewGroup的mFirstTouchTarget不为null啊,那么所有的滑动事件都不会再传递到最底层的View了

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
  • 扩展:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档