安卓基础常识系列旨在短小精悍地提供面试或作业中常用的基础常识,让对安卓还不太了解的小伙伴更快地入门。一同自己在作业中,也无法彻底记住一切的基础细节,写这样的系列文章,能够让自己形成一个更完备的常识体系,一同给自己日后留个常识参阅。

开端的开端

本篇文章依据 Android 12.0 版别,会从源码视点来领会事情从顶层 ViewGroup 往下传递的进程,以及 View 是怎样处理传递过来的事情的。

正文

还记得备忘录里记的定论吗,假如你还不是很了解 View 事情传递机制的原理,直接看备忘录的定论可能会觉得云里雾里,不了解为什么,但假如你耐性看完本篇内容,相信你会了解全部定论。

假如在看此篇之前,你现已了解了 View 传递机制的原理,无妨直接前往备忘录看定论性的常识,会比看这篇更有效率。

条目 描绘
1 事情传递方向为:Actiivty -> Window -> View,假如事情传递给 View 后没有被耗费,那么事情会回到 Activity 的手中,调用 Activity.onTouchEvent() 处理。
2 一个 View 只能从 down 事情开端处理事情,假如它不耗费 down 事情,那么同一事情序列中的其它事情都不会再交给它处理,并且事情将会重新交给它的父 View 处理,即父元素的 onTouchEvent() 会被调用。假如它耗费 down 事情,意味着它能接纳到后续的 move、up事情,假如它不耗费后续的 move、up 事情,那么这些 move、up 事情会消失,父元素的 onTouchEvent() 不会被调用,这些消失的事情终究会传给 Activity 处理。
3 一个父 View 假如决议阻拦子 View 同一事情序列中的某个事情,假如剩余的事情还能传递给它,那么都会交给它来处理,不会再调用 onInterceptTouchEvent() 办法问询。更具体的,假如父 View 从 down 事情开端阻拦,那么事情传递就会到此中止,不会再往子 View 传递。
4 假如一个 down 事情现已被子 View 处理,父 View 在阻拦子 View 能承受的后续事情前,会向子 View 分发一个 cancel 事情,接着父 View 才干接手子 View 的事情。
5 ViewGroup 默许不阻拦事情,其 onInterceptTouchEvent() 办法默许回来 false。
6 View 没有 onInterceptTouchEvent() 办法,事情传递给它,它就会调用 onTouchEvent() 办法。
7 假如 View 设置了 onTouchListener,那么它会在 onTouchEvent() 办法履行前调用,假如 onTouchListener.onTouch() 回来 true,onTouchEvent() 就不会被调用了。
8 一个 View 是否耗费事情,取决于 onTouchEvent() 的回来值,假如该 View 能够接纳事情,并在 onTouchEvent() 做了必定处理,但终究办法回来的成果是 false,那么该事情仍然没有被耗费,事情会传递给其他 View。
9 View 的 onTouchEvent() 办法默许完成里,当 View 不行用时,只需它满意 clickable = true 或 longClickable = true,办法就会回来 true。View 的 longClickable 默许都为 false,clickable 特点要依据状况而定,一般默许支撑点击事情的 View 其 clickable 特点都为 true,比方 Button。默许不支撑点击事情的 View,如 TextView 其 clickable 特点为 false。
10 View 的 onTouchEvent() 办法默许完成里,当 View 可用时,只需它是可点击的,或被设置了提示文本(tool tip),onTouchEvent() 回来 true, 即默许耗费事情,不然回来 false
11 假如 View 能接纳 down 事情,其 onClick() 办法会在 onTouchEvent 接纳 up 事情时调用。
12 事情传递进程是由外向内的,即事情总是先传递给父 View,然后再由父View 传递给子 View,能够经过 requestDisallowInterceptTouchEvent() 办法在子 View 中干涉父 View 的事情分发进程,例如不让父 View 阻拦子 View 的事情。可是 down 事情 除外。
源码剖析 ViewGroup 分发事情进程

回归正题,事情会从顶层 ViewGroup 的 dispatchTouchEvent() 办法传递下去,所以先来看看这部分的源码,剖析完 ViewGroup 的部分再剖析 View 的部分

// ViewGroup 类
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // ...
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
    // Handle an initial down.
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 在接纳到 ACTION_DOWN 事情时,重置状况和标记
        cancelAndClearTouchTargets(ev);
        // 将 mFirstTouchTarget 置空,重置 FLAG_DISALLOW_INTERCEPT 标志位
        resetTouchState();
    }
    // 下面代码查看 是否阻拦此事情
    final boolean intercepted;
    // 假如是 down 事情或 mFirstTouchTarget != null 不为空,
    // mFirstTouchTarget 不为空,表子 View 处理了 down 事情(onTouchEvent 回来 true)
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        // 查看 FLAG_DISALLOW_INTERCEPT 标志位
        // 这个标志能够被子 View 操控,干涉事情的传递方向,除了 down 事情以外
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        // 子 View 没有制止父 View 阻拦事情
        if (!disallowIntercept) {
            // 调用 onInterceptTouchEvent()
            intercepted = onInterceptTouchEvent(ev);
        } else {
            // 不然不允许阻拦事情
            intercepted = false;
        }
    } else {
        // 不然,事情没有被子 View 耗费,ViewGroup 会阻拦掉剩余的事情。
        // (假如在一个事情序列中,子 View 没有处理 down 事情,那么后续的事情都会被ViewGroup阻拦掉)
        intercepted = true;
    }
    // 查看事情是否被半途被撤销了
    final boolean canceled = resetCancelNextUpFlag(this)
            || actionMasked == MotionEvent.ACTION_CANCEL;
    final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
    final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
            && !isMouseEvent;
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    // ViewGroup 不阻拦,将会遍历子View,从深度较高的的子 View 开端传递
    // 直到事情被某子 View 耗费或一切子 View 都遍历完了(事情未被子 View 耗费)
    if (!canceled && !intercepted) {
        // ...
        // 假如是 down 事情
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            /**
             接下来一长串,都是向子View分发 down 事情的进程,运转的成果只需两种:
             1. 事情被子 View 耗费,mFirstTouchTarget 会被赋值,
                指向该子 View,然后 break 退出 for循环
             2. 事情没有被子 View 耗费,那么 mFirstTouchTarget 为 null
             */
            for (int i = childrenCount - 1; i >= 0; i--) {
                // ...
                // dispatchTransformedTouchEvent() 内部会调用 child.dispatchTouchEvent() 办法,将 down 事情
                // 传给子 View,若子 View 耗费掉该事情 dispatchTransformedTouchEvent()
                // 会回来 true, 反之 回来 false
                if (dispatchTransformedTouchEvent(ev, false, mChildren[i], idBitsToAssign)) {
                    // ...
                    // 在 addTouchTarget 里会对 mFirstTouchTarget 赋值
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
            }
        }
    }
    // 假如事情没有被子 View 耗费
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        // mFirstTouchTarget =! null, 阐明在一个事情序列中,down 事情现已被子View处理了
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            // 假如是 down 事情,由于事情在上面分发的进程中被 子View 处理了
            // alreadyDispatchedToNewTouchTarget && target == newTouchTarget 会回来 true
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                // 不然是 move,up事情,分两种状况
                // 1. cancelChild 为 true,即 ViewGroup 决议阻拦事情(intercepted = true)
                // 2. cancelChild 为 false,ViewGroup 不阻拦
                // traget.child 指向的是之前处理了 down 事情的那个子 View
                final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                // 再次调用 dispatchTransformedTouchEvent,由于cancelChild为 true
                // 所以这时候会调用child的dispatchTouchEvent,传递一个cancel事情
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                    handled = true;
                }
                // ...
            }
            target = next;
        }
    }
    // 到此事情分发结束,假如是 up 事情,或许事情 cancelled等
    // 会触发 resetTouchState 重置 mFirstTouchTarget
    if (canceled
            || actionMasked == MotionEvent.ACTION_UP
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        resetTouchState();
    } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
        final int actionIndex = ev.getActionIndex();
        final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
        removePointersFromTouchTargets(idBitsToRemove);
    }
    // ...
    return handled;
}

代码有些长,一份代码需求一同处理 down,move,up 事情的分发,一同剖析起来,逻辑杂乱可能会使读者迷糊,所以我决议将这三个事情类型的分发进程拆开来,独自讲 down,move,up 事情的分发进程,这样会显得逻辑明晰很多,但会导致文章篇幅有些长,也请咱们耐性读完。

最初说过,一个触屏事情序列总是从 ACTION_DOWN 事情开端,ACTION_UP 事情结束,中心会包括数量不定的 ACTION_MOVE 事情。

那么当 down 事情传到 ViewGroup 后,ViewGroup 是怎样分发的呢?

ViewGroup.dispatchTouchEvent() 分发 down 事情

down 事情作为一个起始事情,ViewGroup.dispatchTouchEvent() 办法会先重置一些必要的状况和变量:

// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
    // 1. 在接纳到 ACTION_DOWN 事情时,重置状况和标记
    cancelAndClearTouchTargets(ev);
    // 2. 将 一切 touchTarget 置空,重置 FLAG_DISALLOW_INTERCEPT 标志位
    resetTouchState();
}
// 撤销和铲除之前一切的 touchTarget
private void cancelAndClearTouchTargets(MotionEvent event) {
    if (mFirstTouchTarget != null) {
        boolean syntheticEvent = false;
        if (event == null) {
            final long now = SystemClock.uptimeMillis();
            event = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
            syntheticEvent = true;
        }
      	// 向 touchTarget(View) 传递一个撤销事情,View 对 cancel 事情默许不作为,仅仅收回状况
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            resetCancelNextUpFlag(target.child);
            dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
        }
      	// 1.1 这儿将一切 touchTarget 置空
        clearTouchTargets();
        if (syntheticEvent) {
            event.recycle();
        }
    }
}
// 1.1 将一切 touchTarget 置空
private void clearTouchTargets() {
  TouchTarget target = mFirstTouchTarget;
  if (target != null) {
    do {
      TouchTarget next = target.next;
      target.recycle();
      target = next;
    } while (target != null);
    mFirstTouchTarget = null;
  }
}
// 2. 将 一切 touchTarget 置空,重置 FLAG_DISALLOW_INTERCEPT 标志位
private void resetTouchState() {
    clearTouchTargets();
  	resetCancelNextUpFlag(this);
  	// 在这儿重置 FLAG_DISALLOW_INTERCEPT 标志位
  	mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  	mNestedScrollAxes = SCROLL_AXIS_NONE;
}

TouchTarget 目标:单向链表

上面的代码中提到了很多次 TouchTarget,TouchTarget 是一个目标,名字直译过来便是接触目标,存储了能够承受事情的 View 信息。一个可接纳事情(事情接触点在 View 的边界范围内且该 View 可见)的 View 便是一个 touchTarget。

private static final class TouchTarget {
    // 被接触的子 View
    public View child;
    // 接触点的id,用来区分针对多点触控的状况
    public int pointerIdBits;
    // 单链表中指向的下一个节点的 TouchTarget
    public TouchTarget next;
    @UnsupportedAppUsage
    private TouchTarget() {
    }
		// 经过这个办法结构 touchTarget 目标,传入的是被触碰的 View,以及该接触点的 id
    public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
        if (child == null) {
            throw new IllegalArgumentException("child must be non-null");
        }
        final TouchTarget target;
        synchronized (sRecycleLock) {
            if (sRecycleBin == null) {
                target = new TouchTarget();
            } else {
                target = sRecycleBin;
                sRecycleBin = target.next;
                 sRecycledCount--;
                target.next = null;
            }
        }
        target.child = child;
        target.pointerIdBits = pointerIdBits;
        return target;
    }
		// 收回操作,将 child 置空
    public void recycle() {
        if (child == null) {
            throw new IllegalStateException("already recycled once");
        }
        synchronized (sRecycleLock) {
            if (sRecycledCount < MAX_RECYCLED) {
                next = sRecycleBin;
                sRecycleBin = this;
                sRecycledCount += 1;
            } else {
                next = null;
            }
            child = null;
        }
    }
}

从 TouchTarget 类结构能够看出,它是一个单向链表的结构。在 ViewGroup 中,咱们常常看到 mFirstTouchTarget 特点,它是单向链表的头,指向的是最近被接触的 View。

好的,TouchTarget 介绍到这足够了,回过头来,ViewGroup 在每次分发 down 事情前,重置了 mFirstTouchTarget 单链表,也清空了 FLAG_DISALLOW_INTERCEPT 标志位,

这个标志位能够被子 View 设置,用来干涉 ViewGroup 默许的事情分发进程,这儿先简单提一下,晚点会解析。

往下看:

// ViewGroup 类
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // ...
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
    // Handle an initial down.
		// ...
    // 下面代码查看 是否阻拦此事情
    final boolean intercepted;
    // 假如是 down 事情或 mFirstTouchTarget != null 不为空,
    // mFirstTouchTarget 不为空,表子 View 处理了 down 事情(onTouchEvent 回来 true)
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        // 查看 FLAG_DISALLOW_INTERCEPT 标志位
        // 这个标志能够被子 View 操控,干涉事情的传递方向,除了 down 事情以外
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        // 子 View 没有制止父 View 阻拦事情
        if (!disallowIntercept) {
            // 调用 onInterceptTouchEvent()
            intercepted = onInterceptTouchEvent(ev);
        } else {
            // 不然不允许阻拦事情
            intercepted = false;
        }
    } else {
        // 不然,事情没有被子 View 耗费,ViewGroup 会阻拦掉剩余的事情。
        // (假如在一个事情序列中,子 View 没有处理 down 事情,那么后续的事情都会被ViewGroup阻拦掉)
        intercepted = true;
    }
    // ...
}

接下来会查看 ViewGroup 是否需求阻拦 down 事情。

由于是 down 事情,代码会走进 if 句子,查看 FLAG_DISALLOW_INTERCEPT 标志位是否被子 View 设置,假如设置了,disallowIntercept 会赋值为true,制止 ViewGroup 阻拦事情。但由于此次事情是 down 事情,这个标志位在前面被重置清空了,所以 disallowIntercept 会为 false。换句话说,子 View 无法经过 FLAG_DISALLOW_INTERCEPT 标志位制止父类 ViewGroup 阻拦 down 事情。

证明了第十二条定论: 12. 事情传递进程是由外向内的,即事情总是先传递给父 View,然后再由父View 传递给子 View,能够经过 requestDisallowInterceptTouchEvent() 办法在子 View 中干涉父 View 的事情分发进程,例如不让父 View 阻拦子 View 的事情。可是 down 事情 除外。

所以这时会调用 onInterceptTouchEvent() 问询 ViewGroup 是否需求阻拦 down 事情。

// ViewGroup 类
public boolean onInterceptTouchEvent(MotionEvent ev) {
   	// ...
    return false;
}

上面是 ViewGroup 的默许完成,默许是不阻拦的,一般咱们也不需求改动它的默许完成。

显而易见这儿得出第五条定论:5. ViewGroup 默许不阻拦事情,其 onInterceptTouchEvent() 办法默许回来 false。

所以,ViewGroup 没有阻拦 down 事情,能够分发给它的子 View 了,接着往下看:

// ViewGroup 类
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    // ...
    final boolean intercepted = false;
    // 查看事情是否被半途被撤销了
    final boolean canceled = resetCancelNextUpFlag(this)
            || actionMasked == MotionEvent.ACTION_CANCEL;
    final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
    final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
            && !isMouseEvent;
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    // ViewGroup 不阻拦,将会遍历子View,从深度较高的的子 View 开端传递
    // 直到事情被某子 View 耗费或一切子 View 都遍历完了,事情未被子 View 耗费
    if (!canceled && !intercepted) {
        // ...
        // 假如是 down 事情
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            /**
             接下来一长串,都是向子View分发 down 事情的进程,运转的成果只需两种:
             1. 事情被子 View 耗费,mFirstTouchTarget 会被赋值,
                指向该子 View,然后 break 退出 for循环
             2. 事情没有被子 View 耗费,那么 mFirstTouchTarget 为 null
             */
            for (int i = childrenCount - 1; i >= 0; i--) {
                // ...
              	// 遍历第一步. 
              	final View child = getAndVerifyPreorderedView(preorderedList, children, i);
              	// 查看 View 是否可见或被设置了动画特点以及 TouchEvent 的接触点是否在 View 内
              	// 假如不满意,continue,遍历下一个
              	// 遍历第二步.
              	if (!child.canReceivePointerEvents()
                    || !isTransformedTouchPointInView(x, y, child, null)) {
                  continue;
                }
              	// 遍历第四步.
                // dispatchTransformedTouchEvent() 内部会调用 child.dispatchTouchEvent() 办法,将 down 事情
                // 传给子 View,若子 View 耗费掉该事情 dispatchTransformedTouchEvent()
                // 会回来 true, 反之 回来 false
                if (dispatchTransformedTouchEvent(ev, false, mChildren[i], idBitsToAssign)) {
                    // ...
                  	// 遍历第四步.
                    // 在 addTouchTarget 里会对 mFirstTouchTarget 赋值
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                  	// 跳出 for 循环,事情现已被子 View 耗费
                    break;
                }
            }
        }
    }
		// ...
}

事情没有被 ViewGroup 阻拦,一般状况下,也没有被撤销,所以代码流程会走到 if (!canceled && !intercepted) 循环里边。

假如是 down 事情,就会走进 for 循环,开端遍历子 View。

/**
接下来一长串,都是向子View分发 down 事情的进程,运转的成果只需两种:
1. 事情被子 View 耗费,mFirstTouchTarget 会被赋值,
   指向该子 View,然后 break 退出 for循环
2. 事情没有被子 View 耗费,那么 mFirstTouchTarget 为 null
*/
for (int i = childrenCount - 1; i >= 0; i--) {
  // ...
  // 遍历第一步. 
  final View child = getAndVerifyPreorderedView(preorderedList, children, i);
  // 查看 View 是否可见或被设置了动画特点以及 TouchEvent 的接触点是否在 View 内
  // 假如不满意,continue,遍历下一个
  // 遍历第二步.
  if (!child.canReceivePointerEvents()
      || !isTransformedTouchPointInView(x, y, child, null)) {
    continue;
  }
  // 遍历第三步. 
  // dispatchTransformedTouchEvent() 内部会调用 child.dispatchTouchEvent() 办法,将 down 事情
  // 传给子 View,若子 View 耗费掉该事情 dispatchTransformedTouchEvent()
  // 会回来 true, 反之 回来 false
  if (dispatchTransformedTouchEvent(ev, false, mChildren[i], idBitsToAssign)) {
    // ...
    // 遍历第四步. 
    // 在 addTouchTarget 里会对 mFirstTouchTarget 赋值
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    // 跳出 for 循环,事情现已被子 View 耗费
    break;
  }
}

每次遍历的第一步,会有一个 child 的赋值操作:

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

上面这个办法会回来,假如 preorderedList 不为 null,则从 preorderedList 中取 View,不然从children中取View。preorderedList 也是一个存储子 View 的列表,它与 children 不同的地方在于存储的次序不同

子 View 中 z 轴特点值越高的,在 preorderedList 排的越后

所以假如 preorderedList 不为 null,for (int i = childrenCount - 1; i >= 0; i--) 从后往前遍历的含义便是:显现在最前面的子 View 最先遍历。

反之为 null 的话,就从 children 按默许的绘画次序从后往前遍历。

View 的 z 轴特点

上面读起来可能会有点蒙,这儿简单介绍下 View 的 z 轴特点,在 View 中有一个 getZ() 办法,能够获得 View 在 Z 轴的高度。安卓屏幕是一个二维平面,只需 x,y轴,这个 z 轴怎样了解呢?

View 的 x,y 特点决议了它在父容器中的方位,经过 getZ() 办法,能够获得 View 在父容器中显现的优先级,在同级View 中,假如一个 View 的 getZ() 回来值越高,那它就显现在更前面。

安卓基础知识之View篇(三):源码分析 View 事件分发机制

实践的显现作用如上,假设 ViewGroup 是一个 RelativeLayout,假如咱们不设置其他的定位特点,往RelativeLayout 中增加两个子 View,默许状况是,第二个增加的 View会显现在第一个增加的 View 的上面,假如给第一个子 View 设置一个更高的 getZ() 回来值,那么状况就会反过来,第一个子 View 会掩盖在第二个子 View 的上面。

这儿之所以反复用 getZ() 来叙述 View,是由于 View 中,有 x,y 特点变量,但没有 z 特点变量,getZ() 的计算办法如下:

public float getZ() {    return getElevation() + getTranslationZ();}

由 elevation(高度)以及 translationZ(Z 轴的偏移量)构成。

接下来遍历第二步,查看 View 是否可见或被设置了动画特点以及 TouchEvent 的接触点是否在 View 内。

办法 描绘
View.canReceivePointerEvents() 回来当时View 是否能够接纳点拨事情,假如当时 View 可见,或 被设置了 animation 特点,回来 true,反之回来 false。
ViewGroup.isTransformedTouchPointInView() 假如点拨事情发生在 View 在坐标系中的方位范围内,回来 true,反之回来 false

若第二步回来 false,该子 View 无法承受该事情,直接 continue,越过本次循环,不然,进入第三步。

遍历第三步,向该子 View 分发 down 事情。调用 ViewGroup.dispatchTransformedTouchEvent(),将事情传递给子 View,假如 子 View 耗费掉了该事情,ViewGroup.dispatchTransformedTouchEvent 办法会 回来 true,反之 false。

// ViewGroup 类private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {    final boolean handled;    final int oldAction = event.getAction();    // 假如事情被阻拦,或本来便是一个 cancel action    // 会分发一个cancel事情    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {        // 设置撤销事情(假如自身不是cancel 事情,但被父类阻拦了)        event.setAction(MotionEvent.ACTION_CANCEL);        // 假如没有 child 能够处理事情        if (child == null) {            // 调用 ViewGroup 父类的 dispatchTouchEvent() 办法,也即调用 View 的dispatchTouchEvent(),由于这些办法都是用 ViewGroup 目标调用的,          	// 从而终究会调用 ViewGroup 自己的 onTouchEvent() 办法,ViewGroup 自己处理这个事情            handled = super.dispatchTouchEvent(event);        } else {            // 有的话,调用 child.dispatchTouchEvent()            // 将事情从 ViewGroup 分发到 View            handled = child.dispatchTouchEvent(event);        }        // 将撤销事情传递完后,重置事情类型,假如是父类阻拦了事情导致的撤销,事情仍然会传递到子 View        // 可是是一个撤销事情,View 关于 cancel 事情的默许行为便是不作为,        event.setAction(oldAction);        return handled;    }    // ...    // 省掉了一些代码,一般状况下,省掉的代码运转作用与下面这行代码相同    final MotionEvent transformedEvent = event;    // 接下来进行事情的正常分发    // 假如没有 child 能够处理事情    if (child == null) {        // 调用 ViewGroup 父类的 dispatchTouchEvent() 办法,也即调用 View 的dispatchTouchEvent()        // 从而会调用 ViewGroup 自己的 onTouchEvent() 办法,ViewGroup 自己处理这个事情        handled = super.dispatchTouchEvent(transformedEvent);    } else {        // ...        // 有的话,调用 child.dispatchTouchEvent()        // 将事情从 ViewGroup 分发到 View        handled = child.dispatchTouchEvent(transformedEvent);    }    return handled;}

ViewGroup.dispatchTransformedTouchEvent() 不只能够用来向子 View 分发事情,也能够用来让 ViewGroup 自己处理事情。

先整体剖析一遍该办法吧,从头往下看,假如事情被阻拦,或许自身是一个 cancel 事情,那么就会依据 child 是否为 null 来进行下一步的操作。

假如 child 为 null,会调用 super.dispatchTouchEvent(event), ViewGroup 的父类是 View,所以会调用 View 的 dispatchTouchEvent(event),由于这些办法都是用 ViewGroup 目标调用的,View 的 dispatchTouchEvent() 办法默许会耗费掉事情,因而调用 super.dispatchTouchEvent(event) 的含义实践上便是将事情传给了 ViewGroup 自己处理,假如 ViewGroup 耗费了该事情,那么 handle = true。这儿可能有些绕,是利用了 Java 的多态语言特性,咱们不了解的话,多思考,相信你会明白的,当然也能够谈论或私信问我。

假如 child 不为 null,就调用 child.dispatchTouchEvent(transformedEvent),child 自身是一个 View,所以也即把事情传递给了子 View,假如子 View 耗费了该事情,handled = true。

cancel 事情传递完后,直接 return。

假如不是 cancel 事情,事情也没有被阻拦,就会接着往下走,进行正常的事情分发了,分发逻辑和上面是相同的:

// 接下来进行事情的正常分发// 假如没有 child 能够处理事情if (child == null) {    // 调用 ViewGroup 父类的 dispatchTouchEvent() 办法,也即调用 View 的dispatchTouchEvent()    // 从而会调用 ViewGroup 自己的 onTouchEvent() 办法,ViewGroup 自己处理这个事情    handled = super.dispatchTouchEvent(transformedEvent);} else {    // ...    // 有的话,调用 child.dispatchTouchEvent()    // 将事情从 ViewGroup 分发到 View    handled = child.dispatchTouchEvent(transformedEvent);}

由于向 ViewGroup.dispatchTransformedTouchEvent() 办法中传递的是一个 down 事情,并且 child 也不为 null,因而重复一遍第三步的含义:将 down 事情从 ViewGroup 传给了某个子 View,假如子 View 耗费了该事情(onTouchEvent 回来 true),那么 ViewGroup.dispatchTransformedTouchEvent() 会回来 true,反之回来 false。

接着往下看,假如第三步回来了 true,down 事情被子 View 耗费了,会进入 if 循环。

遍历第四步,履行 addTouchTarget()

if (dispatchTransformedTouchEvent(ev, false, mChildren[i], idBitsToAssign)) {    // ...    // 遍历第四步.     // 在 addTouchTarget 里会对 mFirstTouchTarget 赋值    newTouchTarget = addTouchTarget(child, idBitsToAssign);    alreadyDispatchedToNewTouchTarget = true;    // 跳出 for 循环,事情现已被子 View 耗费    break;}private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {  	final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);  	target.next = mFirstTouchTarget;  	mFirstTouchTarget = target;  	return target;}

addTouchTarget() 实践上也便是用接纳 down 事情的子 View 构建一个新的 TouchTarget,然后将 touchTarget 目标赋值给 mFirstTouchTarget 的头结点,然后退出。

mFirstTouchTarget 会进一步赋值给 newTouchTarget,将 alreadyDispatchedToNewTouchTarget 变量设为 true,表 down 事情现已分发给了 newTouchTarget,随后退出循环。

假如第三步回来了 false,事情虽然传递给了子 View,但子 View在 onTouchEvent() 办法回来了 false,因而事情没有被耗费,此刻不会进入 if 循环,mFirstTouchTarget 不会被赋值,接着遍历下一个子 View,假如 for 循环结束后,mFirstTouchTarget 仍然没有被赋值,那么表示没有事情没有被子 View 耗费,那么这时候需求 ViewGroup 自己处理事情。

接着往下看,代码来到这儿:

// ViewGroup 类@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    // ...    final int action = ev.getAction();    final int actionMasked = action & MotionEvent.ACTION_MASK;    // Handle an initial down.		// ...      // 下面代码查看 是否阻拦此事情    // ...    // Dispatch to touch targets.    if (mFirstTouchTarget == null) {      // 事情没有被子 View 承受并耗费      handled = dispatchTransformedTouchEvent(ev, canceled, null,                                              TouchTarget.ALL_POINTER_IDS);    } else {      // down 事情被子 View 承受并耗费了      TouchTarget predecessor = null;      TouchTarget target = mFirstTouchTarget;      while (target != null) {        final TouchTarget next = target.next;        // 假如是 down 事情,且被子 View 耗费了,alreadyDispatchedToNewTouchTarget 会赋值为 true        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {          handled = true;        } else {          // 不是 down 事情,假如 ViewGroup 需求阻拦事情的话,会经过 dispatchTransformedTouchEvent()           // 向子 View 传递一个 cancel 事情。          final boolean cancelChild = resetCancelNextUpFlag(target.child)            || intercepted;          if (dispatchTransformedTouchEvent(ev, cancelChild,                                            target.child, target.pointerIdBits)) {            handled = true;          }          // 假如是撤销事情,事情分发后,需求将子 View 从单链中去掉,下次有事情过来就不会分发到该该子 View 了          if (cancelChild) {            // 指向单链下一个节点。            mFirstTouchTarget = next;            // 收回当时节点            target.recycler();            target = next;            continue;          }        }        predecessor = target;        target = next;      }    }      // ...  	return handled;}

接下来,判别 mFirstTouchTarget 为不为 null,为 null 的话,down 事情没有被子 View 耗费,需求 ViewGroup 自己处理,调用 dispatchTransformedTouchEvent() 办法,在办法第三个 child 参数传入 null。

还记得上面对 dispatchTransformedTouchEvent() 办法的剖析吗?终究会调用 ViewGroup 自己的 onTouchEvent() 办法处理事情。

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

mFirstTouchTarget 不为 null,阐明 down 事情现已被子 View 耗费了,会进入一个 while 循环,遍历 mFirstTouchTarget 链表,if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 会回来 true,从而 handled 会被赋值为 true。

在单点触碰产生的事情序列中,mFirstTouchTarget 单链只需一个节点,也便是当时接纳接触事情的 View,咱们只需求重视 while 的第一次也是仅有一次循环,遍历的是 mFirstTouchTarget 的头结点,剩余的遍历针对的多点触碰时的状况,咱们不需求重视。

终究到了 ViewGroup.dispatchTouchEvent() 的结尾,将 handled 作为回来值 return 出去。

ViewGroup 分发 down 事情给子 View 处理结束,来份图来总结一下吧:

安卓基础知识之View篇(三):源码分析 View 事件分发机制

剖析进程比较长是由于中心介绍了 TouchTarget、View 的 z 轴特点、dispatchTransformedTouchEvent() 办法等一些必要的内容,接下来不需求再具体介绍了,剖析内容会简略一些。

ViewGroup.dispatchTouchEvent() 分发 move/up 事情

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
    // Handle an initial down.
    // ...
    // Check for interception.
    final boolean intercepted;
    // 只剖析 move、up 事情
    // 子 View 耗费了 down 事情,mFirstTouchTarget != null,此刻接下来的 move、up 事情都应该交给子 View 处理。
    // 但 ViewGroup 能够决议是否阻拦掉子 View 的事情,让接下来的事情由 ViewGroup 处理
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        // 判别 FLAG_DISALLOW_INTERCEPT 标志位
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        // 子 View 没有制止 ViewGroup 阻拦事情
        if (!disallowIntercept) {
            // 调用 onInterceptTouchEvent(),问询父 View 是否阻拦事情。
            intercepted = onInterceptTouchEvent(ev);
        } else {
            // 子 View 制止 ViewGroup 阻拦事情
            intercepted = false;
        }
    } else {
        // 子 View 没有耗费 down 事情,mFirstTouchTarget = null,ViewGroup 自己处理
        intercepted = true;
    }
    // 查看事情是否被撤销了
    final boolean canceled = resetCancelNextUpFlag(this)
            || actionMasked == MotionEvent.ACTION_CANCEL;
    // 事情没撤销,也没有被 ViewGroup 阻拦
    if (!canceled && !intercepted) {
        // 只需 down 事情才会进入 if 句子
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            // ...
        }
    }
    if (mFirstTouchTarget == null) {
        // ViewGroup 自己处理
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            // 假如是 down 事情,且被子 View 耗费了,alreadyDispatchedToNewTouchTarget 会赋值为 true
            // 这儿剖析的是 move、up 事情,所以会走进 else 
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                // 是 move up 事情,假如 ViewGroup 需求阻拦事情的话,cancelChild = true,不然 false
                final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                // 假如 cancelChild = true 会向 子 View 分发一个 cancel 事情
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                    handled = true;
                }
                // 假如是 cancel 事情,事情分发后,需求将子 View 从单链中去掉,下次有事情过来就不会分发到该子 View 了
                // 在单点触碰中,单链只需一个元素,即 mFirstTouchTarget 只需一个 touchTarget,
                // 分发完 cancel 事情后,仅有的 touchTarget 被收回,mFirstTouchTarget = null
                // 下次事情过来时,就由 ViewGroup 自己处理了,这便是 ViewGroup 阻拦的原理。
                if (cancelChild) {
                    // 指向单链下一个节点。
                    mFirstTouchTarget = next;
                    // 收回当时节点
                    target.recycler();
                    target = next;
                    continue;
                }
            }
            predecessor = target;
            target = next;
        }
    }
    return handled;
}

move、up 事情没有初始化操作,因而直接来到了判别 ViewGroup 是否阻拦事情的地方

// Check for interception.final boolean intercepted;// 只剖析 move、up 事情// 子 View 消费了 down 事情,mFirstTouchTarget != null,此刻接下来的 move、up 事情都应该交给子 View 处理。// 但 ViewGroup 能够决议是否阻拦掉子 View 的事情,让接下来的事情由 ViewGroup 处理if (actionMasked == MotionEvent.ACTION_DOWN	|| mFirstTouchTarget != null) {    // 判别 FLAG_DISALLOW_INTERCEPT 标志位    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    // 子 View 没有制止 ViewGroup 阻拦事情    if (!disallowIntercept) {    // 调用 onInterceptTouchEvent(),问询父 View 是否阻拦事情。    intercepted = onInterceptTouchEvent(ev);    } else {    // 子 View 制止 ViewGroup 阻拦事情    intercepted = false;    }} else {    // 子 View 没有处理 down 事情,mFirstTouchTarget = null,ViewGroup 自己处理    intercepted = true;}

假如子 View 消费了 down 事情,mFirstTouchTarget != null,此刻接下来的 move、up 事情都应该交给子 View 处理。假如 ViewGroup 没有阻拦事情的话,intercepted = false,不然 interceped = true。

假如 mFirstTouchTarget = null,子 View 没有消费 down 事情,那么 intercepted = true,事情由 ViewGroup 自己处理,

接着往下走,由于现在不是处理的 down 事情,所以接下来这个 if 句子并不会进入。

// 事情没撤销,也没有被 ViewGroup 阻拦if (!canceled && !intercepted) {    // 只需 down 事情才会进入 if 句子    if (actionMasked == MotionEvent.ACTION_DOWN            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {        // ...    }}

从而直接走向这儿:

if (mFirstTouchTarget == null) {    // ViewGroup 自己处理    handled = dispatchTransformedTouchEvent(ev, canceled, null,            TouchTarget.ALL_POINTER_IDS);} else {    TouchTarget predecessor = null;    TouchTarget target = mFirstTouchTarget;    while (target != null) {        final TouchTarget next = target.next;        // 假如是 down 事情,且被子 View 耗费了,alreadyDispatchedToNewTouchTarget 会赋值为 true        // 这儿剖析的是 move、up 事情,所以会走进 else         if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {            handled = true;        } else {            // 是 move up 事情,假如 ViewGroup 需求阻拦事情的话,cancelChild = true,不然 false            final boolean cancelChild = resetCancelNextUpFlag(target.child)                    || intercepted;            // 假如 cancelChild = true 会向 子 View 分发一个 cancel 事情            if (dispatchTransformedTouchEvent(ev, cancelChild,                    target.child, target.pointerIdBits)) {                handled = true;            }						// 假如是 cancel 事情,事情分发后,需求将子 View 从单链中去掉,下次有事情过来就不会分发到该子 View 了            // while 循环走完后,mFirstTouchTarget = null 下次事情过来时,            // 就由 ViewGroup 自己处理了,这便是 ViewGroup 阻拦的原理。            if (cancelChild) {                // 指向单链下一个节点。                mFirstTouchTarget = next;                // 收回当时节点                target.recycler();                target = next;                continue;            }        }        predecessor = target;        target = next;    }}return handled;

mFirstTouchTarget = null,ViewGroup 自己处理 move up 事情的逻辑与处理 down 事情的逻辑没有什么不同,都是正常按 dispatchTransformedTouchEvent() 的逻辑走,没有什么不同,

只讲一下,down 事情现已由子 View 消费时, ViewGroup 是否阻拦子 View 的 move 和 up 事情逻辑。

// 是 move up 事情,假如 ViewGroup 需求阻拦事情的话,cancelChild = true,不然 false
final boolean cancelChild = resetCancelNextUpFlag(target.child)
        || intercepted;
// 假如 cancelChild = true 会向 子 View 分发一个 cancel 事情
if (dispatchTransformedTouchEvent(ev, cancelChild,
        target.child, target.pointerIdBits)) {
    handled = true;
}
// 假如是 cancel 事情,事情分发后,需求将子 View 从单链中去掉,下次有事情过来就不会分发到该子 View 了
// while 循环走完后,mFirstTouchTarget = null 下次事情过来时,
// 就由 ViewGroup 自己处理了,这便是 ViewGroup 阻拦的原理。
if (cancelChild) {
    // 指向单链下一个节点。
    mFirstTouchTarget = next;
    // 收回当时节点
    target.recycler();
    target = next;
    continue;
}

①. ViewGroup 阻拦子 View 的 move/up 事情

cancelChild 为 true,调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits) ,向之前消费了 down 事情的子 View 分发一个 cancel 事情,重置子 View 的状况。

dispatchTransformedTouchEvent() 办法的具体解析在 ”遍历第三步“那,假如有朋友不记得了办法的流程能够在页面全局搜索 ”遍历第三步“,能够找到。

随后,将子 View 从 mFirstTouchTarget 单链中移除、收回。当 while 循环结束后(关于单点触碰的事情分发,mFirstTouchTarget 单链上的元素应该也只需一个,but anyway),mFirstTouchTarget = null,当下一个 move 或 up 事情到来时,由于 mFirstTouchTarget = null,就会让 ViewGroup 自己处理事情了。这便是 ViewGroup 阻拦事情的原理。

证明了第四条定论:4. 假如一个 down 事情现已被子 View 处理,父 View 在阻拦子 View 能承受的后续事情前,会向子 View 分发一个 cancel 事情,接着父 View 才干接手子 View 的事情

剖析到这儿也能够知道,不管 ViewGroup 从 down、move,up 中的哪个事情开端阻拦,都会导致 mFirstTouchTarget = null,只需 ViewGroup 决议阻拦事情,下一次事情来时,无需再问询 ViewGroup 是否要阻拦事情了,看下面的代码。

// ViewGroup 类
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
	// check interception
	intercepted = onInerceptTouchEvent()
} else {
	intercepted = true;
}

只需 ViewGroup 决议阻拦事情了(调用 onInterceptTouchEvent() 回来 true),那么接下来就不会再调用 onInterceptTouchEvent() 问询了,由于,假设 ViewGroup 在 down 事情开端阻拦,那么会调用一次 onInerceptTouchEvent(),使得 intercpeted = true,ViewGroup自己处理事情,mFirstTouchTarget 就不会被赋值。之后的 move/up 事情来临时就不满意 if 条件了,intercepted 直接赋值为 true。从 move/up 事情开端阻拦也是相同,终究都会导致 mFirstTouchTarget = null,之后的进程同理。

ViewGroup 从 down 事情开端阻拦和从 move 或 up 事情开端阻拦的区别:从down开端阻拦的话,事情分发就会到 ViewGroup 中止,不会再接着往子视图传递。而从 move 或 up 阻拦的话,子视图至少能够接纳 down 事情,假如子视图不耗费承受的 down 事情,那么相同它也接纳不了后续的事情,这点需求咱们结合第二条定论好好领会下。

证明第三条定论:3. 一个父 View 假如决议阻拦子 View 同一事情序列中的某个事情,假如剩余的事情还能传递给它,那么都会交给它来处理,不会再调用 onInterceptTouchEvent() 办法问询。更具体的,假如父 View 从 down 事情开端阻拦,那么事情传递就会到此中止,不会再往子 View 传递。

②. ViewGroup 不阻拦子 View 的 move/up 事情

cancelChild 为 false,相同调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits) ,向之前消费了 down 事情的子 View 分发 move 或 up 事情,走正常分发逻辑,具体流程请看 ”遍历第三步“ 时的办法详解。

假如子 View 不耗费 move、up 事情,dispatchTransformedTouchEvent() 回来 false,那么 ViewGroup 的 dispatchTouchEvent() 办法也会回来 false,这些事情就消失了,也没有时机再调用 ViewGroup 的 onTouchEvent() 办法了,这些消失的事情会传回给 Activity 处理,调用 Activity 的 onTouchEvent() 处理。

// ViewGroup 类
public boolean dispatchTouchEvent(MotionEvent ev) {
  boolean handled = false;
  ...
  // 没有耗费事情时,handled 仍然为 false
  if (dispatchTransformedTouchEvent(ev, cancelChild,
          target.child, target.pointerIdBits)) {
      handled = true;
  }
  return handled;
}
// Activity 类
public boolean dispatchTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    onUserInteraction();
  }
  // ViewGroup 回来 false,window 回来 false,终究走到下面的 onTouchEvent()
  if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
  }
  return onTouchEvent(ev);
}

这儿也证明了第一条和第二条定论:

  1. 事情传递方向为:Actiivty -> Window -> View,假如事情传递给 View 后没有被耗费,那么事情会回到 Activity 的手中,调用 Activity.onTouchEvent() 处理。
  2. 一个 View 只能从 down 事情开端处理事情,假如它不耗费 down 事情,那么同一事情序列中的其它事情都不会再交给它处理,并且事情将会重新交给它的父 View 处理,即父View的 onTouchEvent() 会被调用。假如它耗费 down 事情,意味着它能接纳到后续的 move、up事情,假如它不耗费后续的 move、up 事情,那么这些 move、up 事情会消失,父元素的 onTouchEvent() 不会被调用,这些消失的时刻终究会传给 Activity 处理。

ViewGroup 分发 move/up 事情的进程也剖析完了,期望咱们都能看懂,我再放张图协助咱们了解:

安卓基础知识之View篇(三):源码分析 View 事件分发机制

源码剖析 View 处理事情进程

事情经过了 ViewGroup 的分发,终究来到了 View 这儿,看看 View 是怎样处理事情的?

留意,ViewGroup 是 View 的子类,它也是 View, 我之所以将 ViewGroup 和 View 分开来讲,是依据一种假定,ViewGroup 需求分发事情给子 View,而到了 View 时,View 不需求再分发了,只需决议是否耗费事情。

还记得 ViewGroup 是怎样将事情传给子 View 的吗?在 ViewGroup 的 dispatchTransformedTouchEvent() 办法中,经过调用 child.dispatchTouchEvent() 将事情传给子 View。

因而接下来从 View.dispatchTouchEvent() 办法开端剖析:

View.dispatchTouchEvent() 源码剖析

// View 类public boolean dispatchTouchEvent(MotionEvent event) {  	boolean result = false;    final int actionMasked = event.getActionMasked();    if (actionMasked == MotionEvent.ACTION_DOWN) {        // 有新的事情系列发过来,中止嵌套滑动        stopNestedScroll();    }    // mOnTouchListener 不为空,且 View 是 enable 的话,会先调用 mOnTouchListener.onTouch()    // 假如 onTouch() 回来 true,则办法到此为止,不会再接着调用 onTouchEvent() 办法了。    ListenerInfo li = mListenerInfo;    if (li != null && li.mOnTouchListener != null            && (mViewFlags & ENABLED_MASK) == ENABLED            && li.mOnTouchListener.onTouch(this, event)) {        result = true;    }    // mOnTouchListener.onTouch() 回来 false,调用 onTouchEvent()    if (!result && onTouchEvent(event)) {        result = true;    }    	// ...    return result;}

View.dispatchTouchEvent() 比较于 ViewGroup 就很简单了,由于 View 不像 ViewGroup 需求处理事情的分发,并且 View 也没有 onInterceptTouchEvent() 办法。

有新的 down 事情过来时,假如此刻 View 在滑动状况,会先调用 stopNestedScroll() 让 View 中止滑动。

接着会判别 mOnTouchListener 是否为空,假如不为空则会调用 mOnTouchListener.onTouch() 办法,假如 onTouch() 办法回来 true,result = true,dispatchTouchEvent() 办法会直接回来,不会再调用 View.onTouchEvent() 办法了。

假如 mOnTouchListener 为空,或 mOnTouchListener.onTouch() 办法回来 false,result = false 那么逻辑会走到 View.onTouchEvent() 这儿做进一步处理。

安卓基础知识之View篇(三):源码分析 View 事件分发机制

证明了第七条定论:7. 假如 View 设置了 onTouchListener,那么它会在 onTouchEvent() 办法履行前调用,假如 onTouchListener.onTouch() 回来 true,onTouchEvent() 就不会被调用了。

接着看 View.onTouchEvent() 是怎样处理事情的?

View.onTouchEvent() 源码剖析

// View 类public boolean onTouchEvent(MotionEvent event) {    final float x = event.getX();    final float y = event.getY();    final int viewFlags = mViewFlags;    final int action = event.getAction();    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;    // 假如 View 是 disable 的,直接回来,    if ((viewFlags & ENABLED_MASK) == DISABLED            && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {        // A disabled view that is clickable still consumes the touch        // events, it just doesn't respond to them.        return clickable;    }    if (mTouchDelegate != null) {        if (mTouchDelegate.onTouchEvent(event)) {            return true;        }    }    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {        switch (action) {            case MotionEvent.ACTION_UP:                if (!clickable) {                    break;                }                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                    // take focus if we don't have it already and we should in                    // touch mode.                    boolean focusTaken = false;                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                        focusTaken = requestFocus();                    }                    if (prepressed) {                        // 设置 pressed state                        setPressed(true, x, y);                    }                    // 不是长按事情                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {                        // Only perform take click actions if we were in the pressed state                        if (!focusTaken) {                            // 运用 runnable 来推迟调用,而不是直接调用它,                            // 这样能够让 View 在单击操作开端之前更新 View 的其他可视状况。                            if (mPerformClick == null) {                                mPerformClick = new PerformClick();                            }                            if (!post(mPerformClick)) {                                performClickInternal();                            }                        }                    }                    // 一个 runnable 用于重置 pressed state                    if (mUnsetPressedState == null) {                        mUnsetPressedState = new UnsetPressedState();                    }                    // 重置 pressed state                    if (prepressed) {                        postDelayed(mUnsetPressedState,                                ViewConfiguration.getPressedStateDuration());                    } else if (!post(mUnsetPressedState)) {                        // If the post failed, unpress right now                        mUnsetPressedState.run();                    }                }                break;            case MotionEvent.ACTION_DOWN:                // ...                break;            case MotionEvent.ACTION_CANCEL:                // ...                break;            case MotionEvent.ACTION_MOVE:                // ...                break;        }        return true;    }    return false;}

View 处理事情比 ViewGroup 要简单很多,所以就不将 down、move、up 事情分开剖析了,一同来看。

// 假如 View 是 disable 的,直接回来,if ((viewFlags & ENABLED_MASK) == DISABLED        && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {    // A disabled view that is clickable still consumes the touch    // events, it just doesn't respond to them.    return clickable;}

事情进来时会去判别 View 的 clickable 状况,假如满意 CLICKABLE == true || LONG_CLICKABLE == true || CONTEXT_CLICKABLE == true ,clickable 就会为 true。其间 clickable 和 longClickable 咱们常常用都很了解了,contextClickable 用的很少,在咱们的接触事情中不会用到它,所以能够疏忽掉。

判别完 View 的 clickable 状况后,接着会判别 View 的 enable 状况。假如 View 是 disable 且 它的 PFLAG4_ALLOW_CLICK_WHEN_DISABLED 标志位被设置为 0,即不允许 View 在 disable 状况下被点击,那么 disable 状况下的 View 不会耗费事情。PFLAG4_ALLOW_CLICK_WHEN_DISABLED 标志位默许值为 true

该标志位能够经过调用 setAllowClickWhenDisabled() 设置,但要求设备 api level 在 31 以上:

if (android.os.Build.VERSION.SDK_INT >= 31) {    view.setAllowClickWhenDisabled(true)}

也便是说,假如 View 是 disable 的,只需它的 clickabl 和 longClickable 为 true,那么其 onTouchEvent() 办法就会回来 true,即即使 View 是不行用的,它只需能够被点击,也会耗费事情。

证明了第九条定论:9. View 的 onTouchEvent() 办法默许完成里,当 View 不行能用时,只需它满意 clickable = true 或 longClickable = true,办法就会回来 true。View 的 longClickable 默许都为 false,clickable 特点要依据状况而定,一般默许支撑点击事情的 View 其 clickable 特点都为 true,比方 Button。默许不支撑点击事情的 View,如 TextView 其 clickable 特点为 false。

假如 View 是 disable 的,那么 onTouchEvent() 就结束了,假如 View 是 enable 的,就会接着往下走:

// View 类if (mTouchDelegate != null) {    if (mTouchDelegate.onTouchEvent(event)) {        return true;    }}/*** Sets the TouchDelegate for this View.*/public void setTouchDelegate(TouchDelegate delegate) {  mTouchDelegate = delegate;}

假如 View 设置了署理,那么就会将 onTouchEvent() 剩余的逻辑署理给 touchDelegate,调用 mTouchDelegate.onTouchEvent(event),这个机制与 View.dispatchTouchEvent() 中提到的 onTouchListener 的作业机制相似,这儿就不深入研究了。

接着往下走,进入 if 判别,只需 View 是 clickabke 或 经过调用 setTooltipText()给 View 设置了提示文本,那么就会进入 if 句子:

// View 类// 标志位,表这个 View 在悬停或长按事情中,是否能够显现一个提示文本// 能够的话,(viewFlags & TOOLTIP) == TOOLTIP 会为 truestatic final int TOOLTIP = 0x40000000;if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {  switch (action) {    case MotionEvent.ACTION_UP:      if (!clickable) {        break;      }      // 查看预按压标志位      boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;      // 假如 View 被设置了按压标志位,或预按压标志位      if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {        // take focus if we don't have it already and we should in        // touch mode.        boolean focusTaken = false;        // View 是否能够获取焦点(光标)        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {          focusTaken = requestFocus();        }        // 假如预按压标志位还在,阐明还没有设置按压标志位,        // 此刻马上给 View 设置按压标志位。        if (prepressed) {          // 设置 pressed state          setPressed(true, x, y);        }        // 不是长按事情        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {          // View 没有获取焦点(光标不在它这)          if (!focusTaken) {            // 运用 runnable 来推迟调用,而不是直接调用它,            // 这样能够让 View 在单击操作开端之前更新 View 的其他可视状况。            if (mPerformClick == null) {              mPerformClick = new PerformClick();            }						            // post 履行            if (!post(mPerformClick)) {              performClickInternal();            }          }        }        // 一个 runnable 用于重置 pressed state        if (mUnsetPressedState == null) {          mUnsetPressedState = new UnsetPressedState();        }        // 重置 pressed state        if (prepressed) {          postDelayed(mUnsetPressedState,                      ViewConfiguration.getPressedStateDuration());        } else if (!post(mUnsetPressedState)) {          // If the post failed, unpress right now          mUnsetPressedState.run();        }      }      break;    case MotionEvent.ACTION_DOWN:      // ...      // 假如当时在被包括在一个可翻滚的容器中,设置一个预按压的标志位      // 这个预按压的标志位会在 ViewConfiguration.getTapTimeout()      // 时刻后重置,一同在重置预按压标志位时,会给 View 设置一个      // 按压标志位。      // (预按压标志位是用来推迟设置按压标志位的,当设置了按压标志位后,预按压标志位就会被重置)      if (isInScrollingContainer) {        mPrivateFlags |= PFLAG_PREPRESSED;        if (mPendingCheckForTap == null) {          mPendingCheckForTap = new CheckForTap();        }        mPendingCheckForTap.x = event.getX();        mPendingCheckForTap.y = event.getY();        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());      } else {        // 假如 View 不在一个翻滚容器中,马上设置按压标志位。        setPressed(true, x, y);      }      break;    case MotionEvent.ACTION_CANCEL:      // ...      break;    case MotionEvent.ACTION_MOVE:      // ...      break;  }  return true;}

暂时不看 switch 句子里边有什么,能够看到的是,只需代码进入了 if 句子内,终究会走到 return true,也即 View.onTouchEvent() 回来 true。意味着这证明了第十条定论。

  1. View 的 onTouchEvent() 办法默许完成里,当 View 可用时,只需它是可点击的,或被设置了提示文本(tool tip),onTouchEvent() 回来 true, 即耗费事情,不然回来 false。

switch 句子里边便是对 down、move、up、cancel 事情的分别处理了,关于 move、cancel 的默许处理,咱们并不需求关怀,只需留意下对 down 和 up 的处理即可:

switch (action) {  case MotionEvent.ACTION_UP:    // 显现 tool tip    if ((viewFlags & TOOLTIP) == TOOLTIP) {      handleTooltipUp();    }    // 不行点击时,直接退出    if (!clickable) {      break;    }    // 查看预按压标志位    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;    // 假如 View 被设置了 按压标志位,或预按压标志位    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {      // take focus if we don't have it already and we should in      // touch mode.      boolean focusTaken = false;      // View 是否能够获取焦点(光标)      if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {        focusTaken = requestFocus();      }      // 假如预按压标志位还在,阐明还没有设置按压标志位,      // 此刻马上给 View 设置按压标志位。      if (prepressed) {        // 设置 pressed state        setPressed(true, x, y);      }      // 不是长按事情      if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {        // View 没有获取焦点(光标不在它这)        if (!focusTaken) {          // 运用 runnable 来推迟调用,而不是直接调用它,          // 这样能够让 View 在单击操作开端之前更新 View 的其他可视状况。          if (mPerformClick == null) {            mPerformClick = new PerformClick();          }          // post 履行          if (!post(mPerformClick)) {            performClickInternal();          }        }      }      // 一个 runnable 用于重置 pressed state      if (mUnsetPressedState == null) {        mUnsetPressedState = new UnsetPressedState();      }      // 重置 pressed state      if (prepressed) {        postDelayed(mUnsetPressedState,                    ViewConfiguration.getPressedStateDuration());      } else if (!post(mUnsetPressedState)) {        // If the post failed, unpress right now        mUnsetPressedState.run();      }    }    break;  case MotionEvent.ACTION_DOWN:    // ...    // 假如当时在被包括在一个可翻滚的容器中,设置一个预按压的标志位    // 这个预按压的标志位会在 ViewConfiguration.getTapTimeout()    // 时刻后重置,一同在重置预按压标志位时,会给 View 设置一个    // 按压标志位。    // (预按压标志位是用来推迟设置按压标志位的,当设置了按压标志位后,预按压标志位就会被重置)    if (isInScrollingContainer) {      mPrivateFlags |= PFLAG_PREPRESSED;      if (mPendingCheckForTap == null) {        mPendingCheckForTap = new CheckForTap();      }      mPendingCheckForTap.x = event.getX();      mPendingCheckForTap.y = event.getY();      postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());    } else {      // 假如 View 不在一个翻滚容器中,马上设置按压标志位。      setPressed(true, x, y);    }    break;  case MotionEvent.ACTION_CANCEL:    // ...    break;  case MotionEvent.ACTION_MOVE:    // ...    break;}

先看下,当 down 事情过来时办法做了哪些作业。

View.onTouchEvent() 处理 down 事情

// 假如当时在被包括在一个可翻滚的容器中,设置一个预按压的标志位// 这个预按压的标志位会在 ViewConfiguration.getTapTimeout()// 时刻后重置,一同在重置预按压标志位时,会给 View 设置一个// 按压标志位。// (预按压标志位是用来推迟设置按压标志位的,当设置了按压标志位后,预按压标志位就会被重置)boolean isInScrollingContainer = isInScrollingContainer();if (isInScrollingContainer) {  mPrivateFlags |= PFLAG_PREPRESSED;  if (mPendingCheckForTap == null) {    mPendingCheckForTap = new CheckForTap();  }  mPendingCheckForTap.x = event.getX();  mPendingCheckForTap.y = event.getY();  postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());} else {  // 假如 View 不在一个翻滚容器中,马上设置按压标志位。  setPressed(true, x, y);}

首先判别 View 是否在翻滚容器内,假如是的话,会设置一个 PFLAG_PREPRESSED 标志位,也叫预按压标志位。这个标志位运用来推迟设置按压标志位的。当按压标志位被设置时,预按压标志位会被清空。

为什么要可翻滚的容器(RecyclerView、List等)中,推迟设置按压标志位呢?主要是为了防止用户实践进行的是滑动内容的操作,却呈现了按压状况。

预按压标志位更翔实的剖析,能够点进 isInScrollingContainer()办法内,查看具体完成。假如读者没有搞懂这个标志位具体什么作用,也没有关系,事实上你能够直接疏忽它,疏忽它不会影响了解事情的 View 传递机制。

假如 View 不在翻滚容器中,那么当 View 接纳到 down 事情的时候,就会给View 设置按下状况,让 View 更新能够自己的视图。

down 事情处理结束,在接纳到 up 事情时,View 会做哪些处理呢?

View.onTouchEvent() 处理 up 事情

// 显现 tool tipif ((viewFlags & TOOLTIP) == TOOLTIP) {  handleTooltipUp();}// 不行点击时,直接退出if (!clickable) {  break;}// 查看预按压标志位boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;// 假如 View 被设置了 按压标志位,或预按压标志位if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {  // take focus if we don't have it already and we should in  // touch mode.  boolean focusTaken = false;  // View 是否能够在接触模式下,获取焦点(光标),比方 EditText 这种  if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {    focusTaken = requestFocus();  }  // 假如预按压标志位还在,阐明还没有设置按压标志位,  // 此刻马上给 View 设置按压标志位。  if (prepressed) {    // 设置 pressed state    setPressed(true, x, y);  }  // 不是长按事情  if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {    // View 没有获取焦点,不是 Editable 的类型    if (!focusTaken) {      // 运用 runnable 来推迟调用,而不是直接调用它,      // 这样能够让 View 在单击操作开端之前更新 View 的其他可视状况。      if (mPerformClick == null) {        mPerformClick = new PerformClick();      }      // post 履行      if (!post(mPerformClick)) {        performClickInternal();      }    }  }  // 一个 runnable 用于重置 pressed state  if (mUnsetPressedState == null) {    mUnsetPressedState = new UnsetPressedState();  }  // 重置 pressed state  if (prepressed) {    postDelayed(mUnsetPressedState,                ViewConfiguration.getPressedStateDuration());  } else if (!post(mUnsetPressedState)) {    // If the post failed, unpress right now    mUnsetPressedState.run();  }}

假如需求显现 tool tip 则会先显现。

假如 View 不是 clickable 的,会直接回来,反之会查看 View 的 按压标志位 和 预按压标志位。

只需两个标志位其间一个被设置了,就会接着判别该 View 是否能够在接触模式下,获取焦点(光标),比方 EditText,假如能够获取焦点,则阐明这个 View 是个能够接纳键盘输入事情的控件,就不会调用 onClick() 办法

不行以获取焦点时,才会走以下逻辑:

if (!focusTaken) {  // 运用 runnable 来推迟调用,而不是直接调用它,  // 这样能够让 View 在单击操作开端之前更新 View 的其他可视状况。  if (mPerformClick == null) {  	mPerformClick = new PerformClick();  }  // post 履行  if (!post(mPerformClick)) {  	performClickInternal();  }}// 经过 post 办法,将 action 放到事情队列中等候调用,到达延时调用的作用public boolean post(Runnable action) {  final AttachInfo attachInfo = mAttachInfo;  if (attachInfo != null) {    return attachInfo.mHandler.post(action);  }  getRunQueue().post(action);  return true;}

mPerformClick 是一个 runnable 类型的目标。

// View 类private final class PerformClick implements Runnable {    @Override    public void run() {      	// 终究会调用 onClickListener.onClick() 办法        performClickInternal();    }}

经过 post() 办法,将 PerformClick() 放到事情队列中等候调用,延时调用 onClickListener.onClick()。

为什么要延时调用 onClick(),主要还是为了让 View 能够在 onClick() 办法调用前,能够先更新它的一些可视的状况。保证这些可视状况在 onClick() 履行前更新给用户。比方 View 的 drawable 文件,咱们在 drawable 文件中界说 View 在不同状况下时的背景样式:

<selector xmlns:android="http://schemas.android.com/apk/res/android" >    <item android:state_pressed="false" android:drawable="@color/white"/>    <item android:state_pressed="true" android:drawable="@color/c_f2f2f2"/></selector>

延时调用 onClick,让 View 履行具体点击逻辑前,更新其背景色彩为白色,避免呈现 onClick 现已履行完了,View 才呈现点击的反馈,实践上是一种优化体会的考虑,前文说的预按压状况也能够相似思维了解。

处理 up 事情的终究,重置 View 的 按压状况:

// 重置 pressed stateif (prepressed) {	postDelayed(mUnsetPressedState,		ViewConfiguration.getPressedStateDuration());} else if (!post(mUnsetPressedState)) {  // If the post failed, unpress right now  mUnsetPressedState.run();}

View.onTouchEvent() 就此剖析结束,来张图总结一下:

安卓基础知识之View篇(三):源码分析 View 事件分发机制

终究放一张事情在 Activity、Window、View 之间传递的流程图,让咱们对机制整体有个更全面的知道:

安卓基础知识之View篇(三):源码分析 View 事件分发机制

文章内容参阅

安卓开发艺术探索,任玉刚

安卓12源码

终究的终究

View 事情分发机制的源码剖析就到这了,整体内容十分长,假如读者能够耐性读彻底篇,相信你会很有收成。把握了本篇的常识,之后还想要温习时,能够直接去看备忘录,看概括性的常识。假如看备忘录没想起来再来看本篇内容,这个温习道路听起来就很妙。

兄dei,假如觉得我写的还不错,费事帮个忙呗 :-)

  1. 给俺点个赞被,鼓励鼓励我,一同也能让这篇文章让更多人看见,(#^.^#)
  2. 不必点收藏,诶别点啊,你怎样点了?这多不好意思!
  3. 噢!还有,我维护了一个路由库。。没其他意思,便是提一下,我维护了一个路由库 =.= !!

托付托付,谢谢各位同学!