文章目录
- 前言
- 源码分析
- ViewGroup下的ACTION_DOWN处理
- ViewGroup的ACTION_MOVE处理
- ViewGroup下的ACTION_UP处理
- view 的ACTION_DOWN
- view 的ACTION_MOVE
- view 的ACTION_UP
- 结论
前言
本文基于<<Android开发艺术探索>> 进一步的理解,部分内容摘抄自书籍中.
为什么要写这篇文章?<<Android开发艺术探索>>内容做了非常好的总结但是很多细节都没有贴出需要自己更进一步阅读.
一次触摸事件
一次触摸事件:指从一个ACTION_DOWN,和多个ACTION_MOVE,以及单个ACTION_UP/ACTION_CANCEL组成.
至于ACTION_UP和ACTION_CANCEL区别会在后面的源码分析讲解.
事件处理涉及重要方法
Activity:
--------------dispatchTouchEvent
--------------onTouchEvent (很多人都没留意Activity存在这个方法,所有未被消费的事件都会回调这个函数)
ViewGroup
--------------dispatchTouchEvent
--------------onTouchEvent
--------------onInterceptTouchEvent
View
--------------dispatchTouchEvent
--------------onTouchEvent
另外仅有 ViewGroup才能进行事件分发,而View只能选择消费或者不消费这个事件.
为什么需要事件分发
触摸事件只有一个,但是有可能多个视图叠加的View需要同时处理,这时根据相关业务逻辑决定哪个view进行处理.
上图有两个叠加的ListView,假设我们在子ListView向下滑动此时应该是让父view滑动还是子view滑动?
本文的Demonstrate代码
//MainActivity.java
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}");
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
log("onTouchEvent ${MotionEvent.actionToString(ev.action)}");
return super.onTouchEvent(ev)
}
fun log(msg: String) {
Log.e("MainActivity", "${msg} ")
}
}
//MyViewGroup.java
class MyViewGroup @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.dispatchTouchEvent(ev)
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
log("onInterceptTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
log("onTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.onTouchEvent(ev)
}
fun log(msg: String) {
Log.e("MyViewGroup", "${msg} ")
}
}
//MyView.java
class MyView : View {
constructor(context: Context?) : super(context) {}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
log("onTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.onTouchEvent(ev)
}
fun log(msg: String) {
Log.e("MyView", "${msg} ")
}
}
<!--activity_main.xml-->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.example.MyViewGroup
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#f0f"
android:text="触发网络请求"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.example.example.MyView
android:layout_gravity="center"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#f00" />
</com.example.example.MyViewGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
预览图:
源码分析
知识铺垫
本小结仅仅为了为源码分析做一个铺垫.
所有的点击事件都会从Activity.dispatchTouchEvent进行触发.
如点击上面点击Demonstrate的MyView
输出:
MainActivity: dispatchTouchEvent ACTION_DOWN ---------ACTION_DOWN事件分发的起点
MyViewGroup: dispatchTouchEvent ACTION_DOWN
MyViewGroup: onInterceptTouchEvent ACTION_DOWN
MyView: dispatchTouchEvent ACTION_DOWN
MyView: onTouchEvent ACTION_DOWN
MyViewGroup: onTouchEvent ACTION_DOWN
MainActivity: onTouchEvent ACTION_DOWN
MainActivity: dispatchTouchEvent ACTION_UP ---------ACTION_UP事件分发的起点
MainActivity: onTouchEvent ACTION_UP
我们继续阅读相关源代码:
class Activity{
public boolean dispatchTouchEvent(MotionEvent ev) {
//如果是ACTION_DOWN会调用onUserInteraction函数
//onUserInteraction默认是空实现,你可以在这个函数实现诸如埋点操作
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//getWindow返回PhoneWindow
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果PhoneWindow没有消费那么回调Activity的onTouchEvent函数
//这个结论可以记下来
return onTouchEvent(ev);
}
//一个空实现
public void onUserInteraction() {
}
}
上面我们可以得到几个结论:
结论1: 事件一定从Activity.dispatchTouchEvent开始分发.(其实这里得不出这个结论,但是为了完整性.)
结论2 : ACTION_DOWN事件一定调用Activity.onUserInteraction.
结论3: PhoneWindow没有消费触摸事件都会回调到Activity.onTouchEvent(这里包含ACTION_DOWN.ACTION_MOVE,ACTION_UP).
上面我们分析到会调用PhoneWindow的superDispatchTouchEvent函数,并根据返回值确定是否调用Activity.onTouchEvent,或者直接返回true
//PhoneWindow.java
class PhoneWindow{
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//源码追踪到DecorView
return mDecor.superDispatchTouchEvent(event);
}
}
//DecorView.java
class DecorView extends FrameLayout{
public boolean superDispatchTouchEvent(MotionEvent event) {
//由于继承FrameLayout,所以这里会回调到ViewGroup的dispatchTouchEvent函数(FrameLayout继承ViewGroup且没有重写这个函数)
return super.dispatchTouchEvent(event);
}
}
于是乎这个事件调度变成了Activity调用到ViewGroup.dispatchTouchEvent函数的过程.后文不在分析Activity将事件传递到DecorView,而是简单的理解Activity将事件传递到我们上文的MyViewGroup中(因为DecorView也是一个ViewGroup,我们的自定义类MyViewGroup也是同一类型,这样可以简化我们的分析流程).
简化后
ViewGroup下的ACTION_DOWN处理
本小结仅分析ViewGroup对ACTION_DOWN处理,也是难点
ACTION_DOWN
此时我们假设我们点击了上文的Demonstrate的MyView,此时产生了一个ACTION_DOWN事件.
class Activity{
public boolean dispatchTouchEvent(MotionEvent ev) {
//产生ACTION_DOWN调用onUserInteraction函数
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//这里我们假设直接调用MyViewGroup的dispatchTouchEvent函数
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//假设MyViewGroup没有消费ACTION_DOWN,调用Activity的onTouchEvent函数
return onTouchEvent(ev);
}
//一个空实现
public void onUserInteraction() {
}
}
MyViewGroup继承ViewGroup,所以我们直接看ViewGroup的dispatchTouchEvent
//ViewGroup.java
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//一些安全性检查,这里我们把onFilterTouchEventForSecurity视为返回true
if (onFilterTouchEventForSecurity(ev)) {
//当前返回ACTION_DOWN
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//ACTION_DOWN会重置标志位和取消之前的事件序列
if (actionMasked == MotionEvent.ACTION_DOWN) {
//ACTION_DOWN事件会取消之前的事件,会给上一个正在处理事件的view发送ACTION_CANCEL事件
cancelAndClearTouchTargets(ev);
//重置一些标志位
resetTouchState();
}
//...略
}
return handled;
}
}
我们不需要太关心cancelAndClearTouchTargets函数实现,不过我们需要看下resetTouchState
//ViewGroup.java
class ViewGroup{
protected int mGroupFlags;
private TouchTarget mFirstTouchTarget;
private void resetTouchState() {
//让mFirstTouchTarget为null
clearTouchTargets();
//这里的位操作是把FLAG_DISALLOW_INTERCEPT给取消掉
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
//..略
mFirstTouchTarget = null;
}
}
}
上面标志位和mFirstTouchTarget作用我们后面来讲,我们继续看代码分析
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//一些安全性检查,这里我们把onFilterTouchEventForSecurity视为返回true
if (onFilterTouchEventForSecurity(ev)) {
//当前返回ACTION_DOWN
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//重置mFirstTouchTarget为null
//取消mGroupFlags的FLAG_DISALLOW_INTERCEPT标志位
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//
final boolean intercepted;
//当前actionMasked == MotionEvent.ACTION_DOWN 为true
//mFirstTouchTarget !=null 为false
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//resetTouchState函数取消了FLAG_DISALLOW_INTERCEPT变量所以这里的判断必然为false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//这里必然取反后为true,调用onInterceptTouchEvent函数
if (!disallowIntercept) {
//onInterceptTouchEvent可能会改变ev的action不过这里不需要太关注这种另类情况
intercepted = onInterceptTouchEvent(ev);
//防止ev被修改
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
//略
}
return handled;
}
}
上面的我们可以到如下结论:
结论4: ACTION_DOWN不受到FLAG_DISALLOW_INTERCEPT变量的影响一定会调用到onInterceptTouchEvent
结论5: mFirstTouchTarget如果不为空时有可能受到FLAG_DISALLOW_INTERCEPT影响是否调用onInterceptTouchEvent,而mFirstTouchTarget不为空仅在ACTION_MOVE/ACTION_UP情况下.
mFirstTouchTarget具体作用在后文才能讲解,这里可以理解为保存处理一个完成触摸事件序列的view对象.
另一个问题是如何改变FLAG_DISALLOW_INTERCEPT这个标志位?我们可以看下如下源码
//ViewParent.java
public interface ViewParent {
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
//ViewGroup.java
class ViewGroup implement ViewParent{
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
//已经设置了就直接返回
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return;
}
//设置标志位
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// 透传到这个ViewGroup的父view
//这里会被一层层向上传递
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
}
这里需要注意设置标志位时会一层层向上传递.
继续向下分析dispatchTouchEvent函数,onInterceptTouchEvent默认是空实现直接返回false.
//ViewGroup.java
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//一些安全性检查,这里我们把onFilterTouchEventForSecurity视为返回true
if (onFilterTouchEventForSecurity(ev)) {
//当前返回ACTION_DOWN
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//重置mFirstTouchTarget为null
//取消mGroupFlags的FLAG_DISALLOW_INTERCEPT标志位
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//
final boolean intercepted;
//当前actionMasked == MotionEvent.ACTION_DOWN 为true
//mFirstTouchTarget !=null 为false
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//resetTouchState函数取消了FLAG_DISALLOW_INTERCEPT变量所以这里的判断必然为false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//空实现,默认返回false
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
//ACTION_DOWN不会执行到这
intercepted = true;
}
// actionMasked == MotionEvent.ACTION_CANCEL为false
//resetCancelNextUpFlag 这里返回false,我们不需要关心,
//canceled整体为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
//true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//intercepted返回false取反后,!intercepted为true
//canceled取反为false
if (!canceled && !intercepted) {
//当前是ACTION_DOWN 所以actionMasked为true,后面的两个判断为多点和鼠标相关
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//对象ACTION_DOWN事件这里是0,getActionIndex返回触摸的手指下标
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//子view的数量
final int childrenCount = mChildrenCount;
//如果子view的数量不为0,挑选一个view判断是否能进行事件分发
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//根据绘制的顺序以及子view的z大小进行排序,子view的z的优先级大于绘制顺序
//也就是子view的z越大,越在preorderedList后面,如果没有z数值,那么根据在viewGroup的绘制顺序排序
//越在ViewGroup后面绘制的优先级越大,也就是会在preorderedList后面
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
}
//略
}
}
//略
}
return handled;
}
}
我们看下buildTouchDispatchChildList相关函数的实现
//ViewGroup.java
class ViewGroup{
public ArrayList<View> buildTouchDispatchChildList() {
return buildOrderedChildList();
}
ArrayList<View> buildOrderedChildList() {
final int childrenCount = mChildrenCount;
//ViewGroup的children数组根据绘制顺序添加到数组中,越后面绘制越在集合后面
//仅有一个子view,或者所有的子view 都没有z轴属性,直接返回空对象即可
//后面会直接用children数组直接拿.
//childrenCount <= 1 很好理解,
//!hasChildWithZ() 可以理解为:如果所有的子view都没有z轴那么直接按照children数组优先级倒序取出即可
if (childrenCount <= 1 || !hasChildWithZ()) return null;
if (mPreSortedChildren == null) {
mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
mPreSortedChildren.clear();
mPreSortedChildren.ensureCapacity(childrenCount);
}
//根据绘制顺序和z轴结合得到一个优先级view集合对象.这里算法必须是稳定的
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();
int insertIndex = i;
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
mPreSortedChildren.add(insertIndex, nextChild);
}
return mPreSortedChildren;
}
}
从这里我们可以得到一个重要的知识点,ViewGroup的分发顺序优先级还需要考虑子view的z轴.
我们继续往下深究源码
//ViewGroup.java
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
//...略
//intercepted返回false取反后,!intercepted为true
//canceled取反为false
if (!canceled && !intercepted) {
//当前是ACTION_DOWN 所以actionMasked为true,后面的两个判断为多点和鼠标相关
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//对象ACTION_DOWN事件这里是0,getActionIndex返回触摸的手指下标
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//子view的数量
final int childrenCount = mChildrenCount;
//如果子view的数量不为0,挑选一个view判断是否能进行事件分发
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//根据绘制的顺序以及子view的z大小进行排序,子view的z的优先级大于绘制顺序
//也就是子view的z越大,越在preorderedList后面,如果没有z数值,那么根据在viewGroup的绘制顺序排序
//越在ViewGroup后面绘制的优先级越大,也就是会在preorderedList后面
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//倒序获取view,然后判断哪个view能处理
for (int i = childrenCount - 1; i >= 0; i--) {
//获取下标
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
//获取子view
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//canReceivePointerEvents()如果当前view可见或者正在执行动画返回true
//isTransformedTouchPointInView 判断当前触摸的坐标是否落在view范围内
//整个if判断当前view是否能处理事件,不能处理跳过本次循环寻找下一个view
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//ACTION_DWON事件getTouchTarget一定返回null.后面在分析
newTouchTarget = getTouchTarget(child);
//因为newTouchTarget==null所以if我们跳过
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//非常重要的函数,我们看看这个函数具体的实现
//这个函数大致是将调用view的dispatchTouchEvent作为返回值
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//略
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
//...略
}
}
}
//...略
}
}
我们首先看下dispatchTransformedTouchEvent
//ViewGroup.java
class ViewGroup{
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
//cancel为false,oldAction为ACTION_DWON所以跳过if
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;
}
// 这里博主不大确定,不过从注释看应该是返回有多个触摸点??
final int oldPointerIdBits = event.getPointerIdBits();
//新旧触摸点数量进行校验,如果不相等必然为0
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//状态不一致,异常状态直接返回
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
//这里在没有异常情况newPointerIdBits == oldPointerIdBitstrue
if (newPointerIdBits == oldPointerIdBits) {
//做一些偏移计算,这里的代码不影响分析流程,所以跳过
//hasIdentityMatrix这里应该判断view是否做了一些矩阵变化
//我们这里视为fals
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);
}
// child不为空我们直接看else语句
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//这里由于我们没有滚动和偏移所以这里的计算对我们没有影响
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//分发到MyView.dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
}
上面会根据子view是否消费决定了ViewGroup后续的代码走向,我们将分两种情况讲解.
子view消费ACTION_DOWN
假设所有的MyViewGroup的dispatchTransformedTouchEvent返回true.
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
TouchTarget newTouchTarget = null;
//倒序获取view,然后判断哪个view能处理
for (int i = childrenCount - 1; i >= 0; i--) {
//获取下标
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
//获取子view
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//canReceivePointerEvents()如果当前view可见或者正在执行动画返回true
//isTransformedTouchPointInView 判断当前触摸的坐标是否落在view范围内
//整个if判断当前view是否能处理事件,不能处理跳过本次循环寻找下一个view
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//ACTION_DWON事件getTouchTarget一定返回null.后面在分析
newTouchTarget = getTouchTarget(child);
//因为newTouchTarget==null所以if我们跳过
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//假设函数返回了true
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//这里很重要给newTouchTarget赋值了,newTouchTarget包含子view 的引用
//内部mFirstTouchTarget会被赋值,赋值的对象为view
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//这个标志为记下来
alreadyDispatchedToNewTouchTarget = true;
//结束循环
break;
}
}
}
//break后....因为newTouchTarget不为null跳过具体方法体
if (newTouchTarget == null && mFirstTouchTarget != null) {
//略
}
// mFirstTouchTarget不为空
if (mFirstTouchTarget == null) {
//略这里没比要
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//alreadyDispatchedToNewTouchTarget为true
//target == newTouchTarget为true
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//略
}
//赋值后跳出循环
predecessor = target;
target = next;
}
}
//END dispatchTouchEvent
return handled;
}
//注意mFirstTouchTarget会被赋值
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
}
这里需要背下一个结论子view消费事件,mFirstTouchTarget会被赋值为对应的子view.
子view不消费ACTION_DOWN
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//一些安全性检查,这里我们把onFilterTouchEventForSecurity视为返回true
if (onFilterTouchEventForSecurity(ev)) {
//略
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//intercepted返回false取反后,!intercepted为true
//canceled取反为false
if (!canceled && !intercepted) {
//当前是ACTION_DOWN 所以actionMasked为true,后面的两个判断为多点和鼠标相关
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//略
//子view的数量
final int childrenCount = mChildrenCount;
//如果子view的数量不为0,挑选一个view判断是否能进行事件分发
if (newTouchTarget == null && childrenCount != 0) {
//倒序获取view,然后判断哪个view能处理
for (int i = childrenCount - 1; i >= 0; i--) {
//略 直到循环结束都没有子view消费事件
}
}
//由于mFirstTouchTarget和newTouchTarget都为null,所以这里不需要分析
if (newTouchTarget == null && mFirstTouchTarget != null) {
//...
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 因为没有子view消费,dispatchTransformedTouchEvent内部会调用父类的dispatchTouchEvent函数
//注意第三个参数 传入null
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//略
}
// Update list of touch targets for pointer up or cancel, if needed.
//略
}
return handled;
}
}
我们重新看下dispatchTransformedTouchEvent函数.
//ViewGroup.java
class ViewGroup{
private boolean dispatchTransformedTouchEvent(MotionEvent event,
boolean cancel,
View child,
int desiredPointerIdBits) {
final boolean handled;
//回调到view的dispatchTouchEvent函数
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
}
return handled;
}
}
ViewGroup的ACTION_MOVE处理
假设子view消费了之前的ACTION_DOWN事件
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//当前返回ACTION_MOVE
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//
final boolean intercepted;
//当前actionMasked == MotionEvent.ACTION_DOWN 为false
//mFirstTouchTarget !=null 为true,子view消费了DWON事件后,mFirstTouchTarget会指向它
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//ACTION_MOVE不会重置flag所以拦截事件受FLAG_DISALLOW_INTERCEPT影响
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//空实现,默认返回false
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false;
}
} else {
//ACTION_DOWN不会执行到这
intercepted = true;
}
// actionMasked == MotionEvent.ACTION_CANCEL为false
//resetCancelNextUpFlag 这里返回false,我们不需要关心,
//canceled整体为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// mFirstTouchTarget不为空
if (mFirstTouchTarget == null) {
//略
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//target不为空
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//这里需要根据这里我们要分两种情况分析源码
//ViewGroup的onInterceptTouchEvent返回了true那么,cancelChild为true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//如果cancelChild为true.dispatchTransformedTouchEvent会传递给子view一个cancel事件
//如果cancelChild为false,会将当前事件传给target.child对象
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果是拦截事件这里为true
if (cancelChild) {
//必然为空
if (predecessor == null) {
//重置mFirstTouchTarget对象
mFirstTouchTarget = next;
} else {
//略
}
target.recycle();
target = next;
continue;
}
}
//略
}
}
//这里canceled为true的话会重置所有状态
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//略
}
return handled;
}
}
//下面为dispatchTransformedTouchEvent的cancel为true的源码
//ViewGroup.java
class ViewGroup{
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//由于cancel为true,所以进入循环
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//调用ViewGroup的dispatchTouchEvent函数进行处理ACTION_CANCEL事件
handled = super.dispatchTouchEvent(event);
} else {
//给子view传递一个ACTION_CANCEL事件
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
}
}
//下面为dispatchTransformedTouchEvent的cancel为false的源码
//ViewGroup.java
class ViewGroup{
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
if (child == null)
{
handled = super.dispatchTouchEvent(event);
} else {
//事件直接分发到子view
handled = child.dispatchTouchEvent(event);
}
return handled;
}
}
结论6: 如果子view消费了DOWN事件,而父viewGroup在MOVE拦截了事件,会传入一个cancel给子view.注意此时ViewGroup虽然拦截了MOVE事件,但是本次的MOVE事件不传到ViewGroup的onTouchEvent,而后续事件会传入ViewGroup.onTouchEvent(这个结论有点超纲,不过马上会介绍)
viewGroup拦截后第二个ACTION_MOVE处理
这一小节是基于子View消费了ACTION_DOWN,然后第一个ACTION_MOVE被父ViewGroup拦截后的情况,我们看下这种情况下父ViewGroup如何处理后续的ACTION_MOVE.
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//一些安全性检查,这里我们把onFilterTouchEventForSecurity视为返回true
//当前返回ACTION_MOVE
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//重置mFirstTouchTarget为null
//取消mGroupFlags的FLAG_DISALLOW_INTERCEPT标志位
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//
final boolean intercepted;
//当前actionMasked == MotionEvent.ACTION_MOVE 为false
//mFirstTouchTarget !=null 为false
//整体判断为false
//可见onInterceptTouchEvent拦截move事件后,后续不会在拦截
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//拦截代码不会走
} else {
//执行到这
intercepted = true;
}
// actionMasked == MotionEvent.ACTION_CANCEL为false
//resetCancelNextUpFlag 这里返回false,我们不需要关心,
//canceled整体为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
//true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 由于第三传入null会调用自己父类的view的dispatchTouchEvent函数,而view.dispatchTouchEvent会回调onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//..由于不会执行到这... 略
}
return handled;
}
}
结论7:一个ViewGroup拦截了第一个move事件后,不会在调用拦截函数,且会回调自己的继承类View的dispatchTouchEvent.且View.dispatchTouchEvent会回调到ontouchEvent函数
假设子view没有消费了之前的ACTION_DOWN事件
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//当前返回ACTION_MOVE
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
final boolean intercepted;
//当前actionMasked == MotionEvent.ACTION_DOWN 为false
//mFirstTouchTarget 为空
//所以不会进入拦截函数
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//略
} else {
//直接标记被拦截
intercepted = true;
}
// actionMasked == MotionEvent.ACTION_CANCEL为false
//resetCancelNextUpFlag 这里返回false,我们不需要关心,
//canceled整体为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//intercepted返回true取反后,!intercepted为false
if (!canceled && !intercepted) {
//略
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// // 由于第三传入null会调用自己父类的view的dispatchTouchEvent函数,而view.dispatchTouchEvent会回调onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//略
}
//略
return handled;
}
}
结论8:一个ViewGroup的所有子view都没有消费ACTION_DWON事件,那么不会在判断子view进行分发,且会回调自己的继承类View的dispatchTouchEvent.且View.dispatchTouchEvent会回调到ontouchEvent函数
这里特别注意:我们的root视图是decorview,这个view是我们平时无法触及,所以当我们的子view没有都没有消费DWON,那么decorview不会下发任何事件,但是decorview是可以收到后续事件的.
结论9: 结合decorview的存在,如果DWON事件没有任何view消费,那么不会下发任何事件到下面.但是decorview能收到后续事件.
ViewGroup下的ACTION_UP处理
子view消费了ACTION_DOWN
本小节环境是,子view消费ACTION_DOWN,且父ViewGroup期间并没有拦截MOVE事件
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//当前返回ACTION_UP
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
final boolean intercepted;
//当前actionMasked == MotionEvent.ACTION_DOWN 为false
//mFirstTouchTarget !=null true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//根据FLAG_DISALLOW_INTERCEPT情况是否调用拦截函数
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//空实现,默认返回false
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
//略不会执行到这
}
// actionMasked == MotionEvent.ACTION_CANCEL为false
//resetCancelNextUpFlag 这里返回false,我们不需要关心,
//canceled整体为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// mFirstTouchTarget不为空
if (mFirstTouchTarget == null) {
//略
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//target不为空
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//resetCancelNextUpFlag返回false
//intercepted 决定cancelChild
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//如果intercepted为true,那么会传递一个cancel事件给子view
//如果intercepted为false 那么会传递一个up事件给子view
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//跳出循环
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 由于当前为ACTION_UP所以会调用resetTouchState
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//....略
}
return handled;
}
}
结论10: 如果ViewGroup不拦截Up事件,那么会将事件传递给子view后重置所有变量标识符.如果ViewGroup拦截Up事件,那么子view会受到cancel事件,且事件也不会传递ViewGroup的onTouchEvent其他函数,之后重置所有变量标识符.
子view没消费了ACTION_DOWN
子view没有消耗ACTION_DOWN事件,那么作为ViewGroup必须消耗,否则事件不会传递到这.关于具体消耗事件判断需要下文讲解view在做说明
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//当前返回ACTION_UP
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
final boolean intercepted;
//mFirstTouchTarget !=null 为true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//收到标志位影响是否拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//空实现,默认返回false
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
//canceled整体为false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 因为我们这里假设消费DWON事件
if (mFirstTouchTarget == null) {
// 由于第三传入null会调用自己父类的view的dispatchTouchEvent函数,而view.dispatchTouchEvent会回调onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//略
}
// 如果是Up事件那么重置一些状态
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//略
}
return handled;
}
}
到这里我们分析三个事件在ViewGroup所有分发路径和注意点.特别要记得ACTION_DOWN一定会调用ViewGroup的onInterceptHoverEvent哦(不受标志位影响).
view 的ACTION_DOWN
上面的文章我们分析到ViewGroup会通过dispatchTransformedTouchEvent函数调用View的dispatchTouchEvent.
//view.java
class View{
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
//当前是ACTION_DOWN
final int actionMasked = event.getActionMasked();
//如果是DOWN事件,停止滚动并通知父view的onStopNestedScroll函数
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
//onFilterTouchEventForSecurity视为true即可
if (onFilterTouchEventForSecurity(event)) {
//ListenerInfo包含很多监听器的回调
ListenerInfo li = mListenerInfo;
if (li != null //--------不为空------
&& li.mOnTouchListener != null //---用户是否设置ontouch函数回调---
&& (mViewFlags & ENABLED_MASK) == ENABLED ///-----------如果当前view是enable
&& li.mOnTouchListener.onTouch(this, event) //--------调用用户设置的onTouch函数,函数返回值决定result
) {
result = true;
}
//如果用户自定义的onTouch返回false才会调用自身view.onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
如果是UP事件,停止滚动并通知父view的onStopNestedScroll函数
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
//比较简单不讲解
private ViewParent mNestedScrollingParent;
public void stopNestedScroll() {
if (mNestedScrollingParent != null) {
mNestedScrollingParent.onStopNestedScroll(this);
mNestedScrollingParent = null;
}
}
}
结论11: 如果上层开发者调用了view.setOnTouchListener函数并返回true,那么不会调用view.onTouchEvent.而点击事件是在view.onTouchEvent处理,所以会引起点击事件等无法回调.
我们看到View.dispatchTouchEvent函数比较简单,我们只需要关心View.onTouchEvent函数即可,View.onTouchEvent返回值决定事件是否消费.
//View.java
public class View {
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
//只要是CLICKABLE或者LONG_CLICKABLE是true那么就会消费,哪怕view是DISABLED
//其实enable的情况也一样CLICKABLE或者LONG_CLICKABLE是true那么就会消费.(后文会看到)
//所以可以简单理解CLICKABLE或者LONG_CLICKABLE为true不管view是否可用都会消费事件
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
//调用代理类,如果代理返回true,结束事件
//这里不需要太关心
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//仔细看下这个if的最后一行 return true.
// 也就是说CLICKABLE或LONG_CLICKABLE为true,就消费
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
//略
case MotionEvent.ACTION_DOWN:
//..略
break;
case MotionEvent.ACTION_CANCEL:
//..略
break;
case MotionEvent.ACTION_MOVE:
//..略
break;
}
return true;
}
return false;
}
}
结论12: view的CLICKABLE或者LONG_CLICKABLE是true那么就会消费.
view 的ACTION_MOVE
貌似比较简单,没有太多讲解的
view 的ACTION_UP
//View.java
public class View {
public boolean onTouchEvent(MotionEvent event) {
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//这里有个post操作,需要特别留意,PerformClick类有一个接口函数也是直接调用view的performClick
//这里延迟释放点击回调是为了让这个view在点击事件触发之前有时间更新其他状态
if (!post(mPerformClick)) {
performClick();
}
break;
}
return true;
}
return false;
}
}
我们看下PerformClick和performClick函数
//View.java
private final class PerformClick implements Runnable {
@Override
public void run() {
performClick();
}
}
//这里有个技巧子view重写performClick可以视为点击接收到点击事件
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
//回调点击函数
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
结论
结论1: 事件一定从Activity.dispatchTouchEvent开始分发.(其实这里得不出这个结论,但是为了完整性.)
结论2 : ACTION_DOWN事件一定调用Activity.onUserInteraction.
结论3: PhoneWindow没有消费触摸事件都会回调到Activity.onTouchEvent(这里包含ACTION_DOWN.ACTION_MOVE,ACTION_UP).
结论4: ACTION_DOWN不受到FLAG_DISALLOW_INTERCEPT变量的影响一定会调用到onInterceptTouchEvent
结论5: mFirstTouchTarget如果不为空时有可能受到FLAG_DISALLOW_INTERCEPT影响是否调用onInterceptTouchEvent,而mFirstTouchTarget不为空仅在ACTION_MOVE/ACTION_UP情况下.
结论6: 如果子view消费了DOWN事件,而父viewGroup在MOVE拦截了事件,会传入一个cancel给子view.注意此时ViewGroup虽然拦截了MOVE事件,但是本次的MOVE事件不传到ViewGroup的onTouchEvent,而后续事件会传入ViewGroup.onTouchEvent(这个结论有点超纲,不过马上会介绍)
结论7:一个ViewGroup拦截了第一个move事件后,不会在调用拦截函数,且会回调自己的继承类View的dispatchTouchEvent.且View.dispatchTouchEvent会回调到ontouchEvent函数
结论8:一个ViewGroup的所有子view都没有消费ACTION_DWON事件,那么不会在判断子view进行分发,且会回调自己的继承类View的dispatchTouchEvent.且View.dispatchTouchEvent会回调到ontouchEvent函数
这里特别注意:我们的root视图是decorview,这个view是我们平时无法触及,所以当我们的子view没有都没有消费DWON,那么decorview不会下发任何事件,但是decorview是可以收到后续事件的.
结论9: 结合decorview的存在,如果DWON事件没有任何view消费,那么不会下发任何事件到下面.但是decorview能收到后续事件.
结论10: 如果ViewGroup不拦截Up事件,那么会将事件传递给子view后重置所有变量标识符.如果ViewGroup拦截Up事件,那么子view会受到cancel事件,且事件也不会传递ViewGroup的onTouchEvent其他函数,之后重置所有变量标识符.
结论11: 如果上层开发者调用了view.setOnTouchListener函数并返回true,那么不会调用view.onTouchEvent.而点击事件是在view.onTouchEvent处理,所以会引起点击事件等无法回调.
结论12: view的CLICKABLE或者LONG_CLICKABLE是true那么会消费.
ViewGroup才能事件分发,而View仅能选择消费与否