版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
事件分发在各个版本的 API 都有所改动,这边是基于 25 进行学习。
一、onClick 与 onTouch
先看一段简单的代码:
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyButton button = (MyButton) findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.i(TAG, "onClick 事件");
}
});
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
//return false 表示不消耗此事件,true 表示消耗
Log.i(TAG, "onTouch 事件");
return false;
}
});
}
}
在代码中同时设置了 onClick 与 onTouch 的监听,onTouch 方法里面还有个返回值 boolean,false 表示不消耗此事件,则这次事件能继续往下传播;true 表示消耗此事件,不能触发 onClick 事件。先来分析下这里的源码是怎么写的,为什么为这样。
查看 setOnClickListener 和 setOnTouchListener 源码,默认是调用 View 里面的方法,把对应的监听存储在 View 下的 ListenerInfo 中。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
点击事件最终会调用 View 下的 dispatchTouchEvent 方法,具体后面再讲,这边先查看 View 中的 dispatchTouchEvent 方法。
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
在 dispatchTouchEvent 方法里面设置了一个 Boolean 变量 result ,初始值为 false。在32行的时候调用 mOnTouchListener 的 onTouch 方法,如果 onTouch 返回 true 的话,则把 result 设置为 true。继续往下, if (!result && onTouchEvent(event)) 中,!result 则为 false,所以后面的 onTouchEvent(event) 方法不执行。
我们看一下 View 中的 onTouchEvent(event),在 onTouchEvent(event) 中,当事件为 MotionEvent.ACTION_UP 的时候,调用 performClick(), performClick() 调用了 mOnClickListener 的 onClick 方法。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
所以,当 mOnTouchListener 的 onTouch 方法返回 true 的话,result 值变为 true。 if (!result && onTouchEvent(event)) 中,!result 则为 false,后面的 onTouchEvent(event) 方法不执行,也就不会执行 mOnClickListener 的 onClick 方法。
注:onClick 是在 View 的 onTouchEvent(event) 方法中被调用,自定义控件如果重写了 onTouchEvent(event) 方法且不调用 super.onTouchEvent(event) 则一样不会调用到 onClick 方法。
二、ViewGroup 事件分发
当触摸屏幕的时候,底层通过传感器获取到触摸的坐标,底层通过 C 、C++ 调用到 PhoneWindow,PhoneWindow 会间接调用到 Activity 的 dispatchTouchEvent,这边就从 Activity 开始分析。
1.事件调用顺序
在事件分发中,主要有三个方法:onTouchEvent、onInterceptTouchEvent 和 dispatchTouchEvent。我们先来验证下这三个方法调用的顺序。
自定义 View:
public class MyButton extends View {
private final String TAG = "MyButton";
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent 事件:" + event.getAction());
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(TAG, "dispatchTouchEvent 事件:" + event.getAction());
return super.dispatchTouchEvent(event);
}
}
如果自定义 View 不重写 onTouchEvent 方法的话,默认调用 View 的 onTouchEvent 方法,该方法返回 true,则 ViewGroup 和 Activity 无法触发 onTouchEvent 方法。
自定义 ViewGroup:
public class MyViewGroup extends RelativeLayout {
private final String TAG = "MyViewGroup";
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent 事件:" + event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG, "onInterceptTouchEvent 事件:" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent 事件:" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
}
直接继承 RelativeLayout 是因为 RelativeLayout 里面没有对这三个方法进行重写,后面布局也方便。
MainActivity:
public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyButton button = (MyButton) findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.i(TAG, "onClick 事件");
}
});
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
//return false 表示不消耗此事件,true 表示消耗
Log.i(TAG, "onTouch 事件" + motionEvent.getAction());
return false;
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent 事件:" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent 事件:" + event.getAction());
return super.onTouchEvent(event);
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<com.xiaoyue.androidevent.MyViewGroup
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.xiaoyue.androidevent.MainActivity">
<com.xiaoyue.androidevent.MyButton
android:id="@+id/btn"
android:background="@color/colorPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.xiaoyue.androidevent.MyViewGroup>
运行后点击一次结果:
09-12 22:22:29.734 5186-5186/com.xiaoyue.androidevent I/MainActivity: dispatchTouchEvent 事件:0
09-12 22:22:29.734 5186-5186/com.xiaoyue.androidevent I/MyViewGroup: dispatchTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MyViewGroup: onInterceptTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MyButton: dispatchTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MainActivity: onTouch 事件0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MyButton: onTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MyViewGroup: onTouchEvent 事件:0
09-12 22:22:29.735 5186-5186/com.xiaoyue.androidevent I/MainActivity: onTouchEvent 事件:0
09-12 22:22:29.804 5186-5186/com.xiaoyue.androidevent I/MainActivity: dispatchTouchEvent 事件:1
09-12 22:22:29.804 5186-5186/com.xiaoyue.androidevent I/MainActivity: onTouchEvent 事件:1
可见这三个方法的顺序是:dispatchTouchEvent、onInterceptTouchEvent、onTouch。
2.Activity 的 dispatchTouchEvent
前面说底层通过 Phonewindow 间接调用到 Activity 的 dispatchTouchEvent。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
当事件为 MotionEvent.ACTION_DOWN 的时候,会调用 onUserInteraction 方法,这是一个空的方法,是用来让用户重写的,这边可以忽略。
当 getWindow().superDispatchTouchEvent(ev) 这个返回 true 的时候,dispatchTouchEvent 也直接返回 true,这时候不调用 Activity 的 onTouchEvent 方法。
getWindow( ) 获取到的是 Window 这个抽象类,这边 Window 的实现类是 PhoneWindow(在 (一)UI绘制流程-源码分析 http://blog.csdn.net/qq_18983205/article/details/71601099 有提到)。
接着查看 PhoneWindow 的 superDispatchTouchEvent 方法。
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
直接调用 mDecor 的 superDispatchTouchEvent 方法,mDecor 是一个 DecorView,继续看 DecorView 中该方法的实现。
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView 直接调用父类的 dispatchTouchEvent,DecorView 的父类是 FrameLayout,但是 FrameLayout 没有 dispatchTouchEvent 这个方法,所以直接调用到 FrameLayout 的父类 ViewGroup 中的 dispatchTouchEvent 方法。
这边整理下流程:
3. ViewGroup 的 dispatchTouchEvent
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
//如果是其他触摸设备,比如触摸笔,则调用其他设备的 onTouchEvent
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//是否有辅助功能
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
//onFilterTouchEventForSecurity 进行一个事件的安全检查,正常都是 true
if (onFilterTouchEventForSecurity(ev)) {
//获取事件,计算 MASK,确认是 down、move 还是 up。
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//对 down 事件的处理,对于一次触摸事件来说,down 事件是一个开始
//在这边进行一些初始化的操作,比如清除手势,状态等
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
//这里有个重要的操作是将 mFirstTouchTarget 置空,具体在下面注解,可以先看
cancelAndClearTouchTargets(ev);
//为了当前的触摸循环(从 down 开始)重置所有的触摸状态
resetTouchState();
}
//intercepted 表示检查是否拦截,标记 ViewGroup 是否拦截该触摸事件
// Check for interception.
final boolean intercepted;
//是 down 事件或者 mFirstTouchTarget 不等于空
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept 的值表示是否不允许进行拦截
//true:子 View 不需要父视图拦截事件
//false:子 View 需要父视图拦截事件
// 可以通过viewGroup.requestDisallowInterceptTouchEvent(boolean) 对 disallowIntercept 的值进行设置
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//disallowIntercept 为true,ViewGroup 不调用 onInterceptTouchEvent 进行拦截
if (!disallowIntercept) {
//事件传递流程:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent 中 onInterceptTouchEvent 在 dispatchTouchEvent之后的原因
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//禁止拦截时,事件拦截标志位设置为 false
intercepted = false;
}
} else {
//当前为 down 事件,并且 mFirstTouchTarget 为空是,设置 intercepted = true,表示改事件被 ViewGroup 拦截
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
//检查事件是否被取消了
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//事件分发。不是事件 cancel ,并且事件没有被拦截
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//处理 down 事件,这里比较复杂,也是最重要的一块
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//当前 ViewGroup 含有子控件
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//进行子控件重排序
//为了处理界面上重叠的两个 ViewGroup,重叠的两个都能接收到触摸事件,在上层的先处理,所以需要在这边进行子控件的重排序
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历集合,开始事件分发
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
//从重排序的集合里面取子控件
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
//辅助功能的处理
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//当前 ViewGroup 不能接收事件或点击点不在该子控件范围内,直接跳过,进行下一个循环
//canViewReceivePointerEvents 方法可以看出,不能接收事件的有:处于动画执行过程中、子控件不可见
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//执行到这里,子控件决对可以接收事件
//第一次 down 的时候 newTouchTarget 为空,在 move 的时候 newTouchTarget 不为空,这时候会去直接找到接收点击事件的 View(move 动作是触发多次的,这样就可以不必每次去遍历寻找)
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//找到接收触摸事件的子 View,跳出循环
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent 是真正做事件分发的方法
//将触摸事件传递给子 View,进行递归调用 dispatchTouchEvent 方法
//在 dispatchTouchEvent 中,如果子 View 为 ViewGroup,继续递归调用ViewGroup 的 dispatchTouchEvent,如果子 View 为 View 的话,调用 View 的 dispatchTouchEvent,即我们最上面讲的,View 的 dispatchTouchEvent 会调用 View 的 onTouchEvent
//dispatchTransformedTouchEvent 返回 true,表示子 View 接收了触摸事件
//dispatchTransformedTouchEvent 返回 false 的话,addTouchTarget 方法不被执行,导致 mFirstTouchTarget 为空,所以当前的子 View 无法处理后续的 move 和 up 事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//给 newTouchTarget 开始赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//alreadyDispatchedToNewTouchTarget 表示触摸事件已经分发给新的 TouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//到这里来还没有找到子 View 接收触摸事件并且 mFirstTouchTarget 不为空
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
//newTouchTarget 指向了最初的 TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
//up 和 move 不会经过上面那复杂的逻辑,直接从这开始
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//mFirstTouchTarget 为空,说明该触摸事件还没有被任何一个子 View 接收,此时 ViewGroup 会像一个 View 一样去调用 dispatchTouchEvent(即在 dispatchTouchEvent 方法中调用 onTouchEvent 方法,注意第三个参数这时候是 null)
//所以,如果子 View 的 onTouchEvent 方法返回 true 的时候,则他的上一次 ViewGroup 的 onTouchEvent 不会被执行
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//mFirstTouchTarget 不为空,说明该触摸事件被子 View 接收了,这样该子 View 可以继续处理后续的 move 和 up 事件
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//这里主要处理非 down 事件时候,进行递归调用 dispatchTransformedTouchEvent
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
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;
}
}
//up 和 cancle 的时候,
// Update list of touch targets for pointer up or cancel, if needed.
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);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
在 ViewGroup 里,通过递归,最终调到我们触摸的自定义 ViewGroup 的 dispatchTouchEvent,自定义的 ViewGroup 调用 super.dispatchTouchEvent,根据上面源码分析,这时候调用到自定义 ViewGroup 的 onInterceptTouchEvent 方法。
继续往下递归一次,则调用到自定义 View 的 dispatchTouchEvent 方法。
cancelAndClearTouchTargets
private void cancelAndClearTouchTargets(MotionEvent event) {
//如果是第一次触发 down 的时候,mFirstTouchTarget 为空,直接跳过
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 是 ViewGroup 的一个内部类,记录多手指操作(最多32)的一个单向链表。封装了被触摸的 View 及这次触摸所对应的ID
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
//清除 mFirstTouchTarget,以及把 mFirstTouchTarget 置空
clearTouchTargets();
if (syntheticEvent) {
event.recycle();
}
}
}
clearTouchTargets
private void clearTouchTargets() {
//全部置为初始化状态
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
dispatchTransformedTouchEvent
真正做事件分发的。会调用子控件的 dispatchTouchEvent,形成递归,最终调用到触发事件的自定义 View 的 dispatchTouchEvent。
dispatchTransformedTouchEvent 会调用递归调用 dispatchTouchEvent 最终调用到 onTouchEvent
所以 dispatchTransformedTouchEvent 的返回值实际上是由 onTouchEvent 决定的.。
比较重要是第三个参数有时候为 null。当 child 为空的时候,调用 super.dispatchTouchEvent(event),这调用的是 View 的 dispatchTouchEvent 方法,在最开始就讲了,View 的 dispatchTouchEvent 会调用 onTouchEvent 方法。child 不为空的时候,开始递归,直到 child 是一个 View,最终也会调用到 onTouchEvent 方法。
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();
//处理 cancle 事件
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;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
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;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
//调 View 的 dispatchTouchEvent
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);
}
// Perform any necessary transformations and dispatch.
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());
}
//调用子控件的 dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
注:在 dispatchTouchEvent 源码上提到:通过viewGroup.requestDisallowInterceptTouchEvent(boolean) 对 disallowIntercept 的值进行设置。
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
这里要注意的是,当事件为 down 的时候, resetTouchState 会重置 mGroupFlags 值,导致disallowIntercept 会一直为 false,即一定会进行过滤。所以要设置不拦截只能在 down 事件之后设置父控件不在继续进行拦截。那么想要在 down 事件不进行拦截,只能在 onInterceptTouchEvent 的 down 事件处理返回 false。
4.流程总结
这边直接上一个流程图:
这个是个人的理解,返回的箭头表示方法执行完后的返回。如果有多层 ViewGroup,是跟画出来 ViewGroup 一样的流程再加一层。
网络上现在很多都是画成 U 形,在 View 的 onTouchEvent 直接回调到 ViewGroup 的 onTouchEvent,从源代码上看肯定不是这样的,onTouchEvent 只有在 View 类的 dispatchTouchEvent 方法中被调用。