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