安卓事件机制原理

其实要想很好的理解安卓事件机制,最好的方式就是本身撸一个简化的版本。也就是提取阅读源码后的思想。java

废话很少说,直接开始。若是没有阅读安卓事件机制源码的,能够参考这篇安卓事件分发机制源码详读 。 咱们知道事件是从 Activity 开始分发,最终回到 ViewGroup 的 dispatchTouchEvent(event) 。这里本质上就是模仿,新建一个安卓工程,并添加一个 java 库的 lib, 注意是 java lib。git

为了提取思想,咱们也是仿造安卓事件机制部分源码。这里也新建一个 MyViewGroup 的 java 类继承自 MyView, 由于事件机制中 ViewGroup 也是继承自 View,这里也是作了类似的操做。咱们在使用手势的时候,应该对 MotionEvent 确定是不陌生的,这个类中封装了当前点相对于控件的 x,y 坐标,以及当前的事件类型,好比 ACTION_DOWN、ACTION_MOVE、ACTION_UP。 固然这里只是简化了的。github

首先阐述核心原理,在 MyViewGroup 中保存添加的子视图,当一个事件来的时候先来到 MyViewGroup, 而后倒叙遍历查找容器的的子视图是否有被符合点击范围的,若是符合。则将事件分发给这个子视图,子视图若是是容器,则会继续遍历其子视图。若是是子视图就检查是否拦截事件,若是不拦截就会返回到父视图的 onTouchEvent,父视图能够选择拦截,反之继续往上层容器返回事件。bash

先来看 MyViewGroup 的 dispatchTouchEvent(event)ide

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        System.out.println("当前视图容器的名称: " + super.name);
        int actionMasked = event.getActionMasked();
        boolean intercepted = false;

        boolean handled = false;
        
        // 当按下事件来
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 容器视图能够重写此方法返回 true 拦截事件
            intercepted = onInterceptTouchEvent(event);
        }

        boolean alreadyDispatchedToNewTouchTarget = false;
        // 事件未被拦截也没有取消
        if (!intercepted && actionMasked != MotionEvent.ACTION_CANCEL) {
            // 获取按下的 x, y 距离视图的左上点
            float x = event.getX();
            float y = event.getY();
            int childrenCount = mChildrenCount;
            MyView[] children = mChildren;
            
            // 采起倒叙遍历的方式,由于通常日后添加的视图都会在最上面可见。
            for (int i = childrenCount - 1; i >= 0; i--) {
                MyView child = children[i];

                // 检查按下的坐标是否在 view 上.
                if (!isTransformedTouchPointInView(x, y, child)) {
                    System.out.println("未点击的view上: " + child.name);
                    continue;
                }

                // 将事件交给 child 来处理,由 child 决定是否消费
                TouchTarget newTouchTarget;
                if (dispatchTransformedTouchEvent(event, child)) {
                    newTouchTarget = addTouchTarget(child);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
            }
        }

        // 若是没有事件被消费就会将事件交给容器来处理.
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(event, null);
        } else {
            if (alreadyDispatchedToNewTouchTarget) {
                handled = true;
            }
        }

        return handled;
    }

复制代码

代码中有详细的注释, 接着来看 dispatchTransformedTouchEvent(event), 这个方法就很简单了,就是根据 child 判断,是否继续传递,仍是将事件返回给父视图post

private boolean dispatchTransformedTouchEvent(MotionEvent event, MyView child) {
        boolean handled = false;

        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }

        return handled;
    }

复制代码

怎么检查手势是否在按下的范围呢?isTransformedTouchPointInView(float x, float y, MyView child), 其实也很简单,也就是 x 的起点是否大于左边 mLeft且小于右侧 mRight, y 轴大于顶部 mTop, 小于底部 mBottom。ui

private boolean isTransformedTouchPointInView(float x, float y, MyView child) {
        if (x >= child.mLeft && x <= child.mRight && y >= child.mTop && y <= child.mBottom) {
            return true;
        }

        return false;
    }
复制代码

接下来经过调试来验证代码,首先没有任何视图消费事件,看看是否是符合安卓事件机制的分发过程。当运行后,看到以下打印,能够看到若是视图不消费事件就会将事件回传。并调用 视图 onTouchEvent。spa

第一个验证符合,接下来咱们让容器拦截掉事件。看看还会不会往下传递, onInterceptTouchEvent 返回true。调试

// container 容器返回 true
 public boolean onInterceptTouchEvent(MotionEvent event) {

    return true;
}
复制代码

能够看到只有 container 容器的 onTouchEvent 被调用。第二个验证符合。 code

第三个验证来看,子视图若是设置了 setOntouchListener 后还会不会调用 onTouchEvent

myView.setOnTouchListener(new MyView.OnTouchListener() {
            @Override
            public boolean onTouch(MotionEvent event) {
                System.out.println("myView 视图的 onTouch 方法被调用.");
                return true;
            }
    });

复制代码

能够看到没有打印任何相关 onTouchEvent 的信息, 咱们返回 false 看看。

myView.setOnTouchListener(new MyView.OnTouchListener() {
            @Override
            public boolean onTouch(MotionEvent event) {
                System.out.println("myView 视图的 onTouch 方法被调用.");
                return false;
            }
    });
复制代码

是否是就先打印了 onTouch, 而后再打印了 onTouchEvent。 因为没有事件被消费,因此事件最终会被回传到 container 容器。

验证最后一个,就是点击事件确定是在触摸事件以后被调用,前提是 onTouch 没有拦截。

myView.setOnTouchListener(new MyView.OnTouchListener() {
            @Override
            public boolean onTouch(MotionEvent event) {
                System.out.println("myView 视图的 onTouch 方法被调用.");
                return false;
            }
        });


        myView.setOnClickListener(new MyView.OnClickListener() {
            @Override
            public void onClick(MyView view) {
                System.out.println("myView 视图的 onClick 方法被调用.");
            }
        });
复制代码

OK, 符合验证。若是 onTouch 返回 true。

myView.setOnTouchListener(new MyView.OnTouchListener() {
            @Override
            public boolean onTouch(MotionEvent event) {
                System.out.println("myView 视图的 onTouch 方法被调用.");
                return true;
            }
        });


        myView.setOnClickListener(new MyView.OnClickListener() {
            @Override
            public void onClick(MyView view) {
                System.out.println("myView 视图的 onClick 方法被调用.");
            }
        });
复制代码

onClick 方法就不会被调。

这就是事件机制流程,至于不少细节未去处理。好比安卓事件机制维护了一个 TouchTarget, 它本职上是一个链表。经过 mFirstTouchTarget 来指向它的头节点,若是找到一个 view 须要被处理事件的时候就用头节点去记录它。以便后续的事件来的时候能够很快的找处处理的视图。

最后若是想看源码的,能够克隆下载查看,AndroidMotionEvent

相关文章
相关标签/搜索