0%

Android-View原理与实践

概述

注:本文基于Android 10源码,为了文章的简洁性,引用源码的地方可能有所删减。

本文主要学习一下与View相关的一些原理与实践,如常用的 View.post 和 ViewTreeObserver 的原理,以及老生常谈的 View 事件分发机制的源码解读,自定义 View 及其滑动冲突的解决方式等。有关Android绘制相关的看Android-View绘制原理

View.post

View.post

1
2
3
4
5
6
7
8
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}

Android-Window机制原理可知,mAttachInfo的赋值在AT.handleResumeActivity后调用dispatchAttachedToWindow方法,因此在Activity.onCreate时,mAttachInfo为null,因此可以接下来看看getRunQueue方法:

1
2
3
4
5
6
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}

HandlerActionQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;

public void post(Runnable action) {
postDelayed(action, 0);
}

public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

synchronized (this) {
if (mActions == null) {
// 初始化一个具有4个元素的数组
mActions = new HandlerAction[4];
}
// 将handlerAction添加到mActions中
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}

// ...

private static class HandlerAction {
final Runnable action;
final long delay;

public HandlerAction(Runnable action, long delay) {
this.action = action;
this.delay = delay;
}

public boolean matches(Runnable otherAction) {
return otherAction == null && action == null
|| action != null && action.equals(otherAction);
}
}
}

ViewRootImpl.performTraversals

performTraversals是整个View树绘制流程的发起者,从这个方法开始才会执行View的measure,layout,以及draw动作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// performTraversals的调用者
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}

private void performTraversals() {
// ...
getRunQueue().executeActions(mAttachInfo.mHandler);
// ...
performMeasure();
// ...
performLayout();
// ...
performDraw();
}

// HandlerActionQueue
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}

mActions = null;
mCount = 0;
}
}

ViewRootImpl.performTraversals()方法中,执行getRunQueue().executeActions(mAttachInfo.mHandler)传入了Handler,这个Handler从ViewRootImple的代码中可知是主线程的Handler。

在上述代码中,Choreographer 用来处理 Vsync 信号,当接收到 Vsync 信号后,会通过主线程的 Handler 发送消息,然后执行 performTraversals 中的绘制流程,即 performTraversals 的执行也是以Runnable的形式发送消息至主线程消息队列然后执行,那么在performTraversals()中再将我们通过View.post()添加到主线程的Runnable,就需要等待performTraversals()的代码执行完毕以后才能执行,那么View.post的Runnable代码就只能在执行完View的测量布局绘制以后才能执行,因此通过这种方法我们就可以获取到View的宽和高了。

小结

在onCreate中调用View.post方法时,View会先将Runnable放入到一个数组中,当ViewRootImpl执行View的绘制流程时,performTraversals方法会将这个Runnable放入主线程的消息队列之中,又因为performTraversals方法本身就是通过主线程的消息队列执行的,因此当执行完performTraversals方法也就是完成View的绘制流程后,才会执行View.post传入的Runnable。

有时候View的绘制工作并不是依次能够成功的,因此可能会重新发消息到主线程再执行绘制工作,这会导致View的宽或高以及位置发生变化,这个消息就会排在View.post()发送的消息的后边,因此View.post()获取的宽高可能不正确。

ViewTreeObserver

概述

ViewTreeObserver用来注册监听器,在ViewTree发生变化时收到回调,不能被外部实例化,需要通过android.view.View#getViewTreeObserver()方法来获取。在Android中,所有视图由View和View的子类组成,ViewGroup也是view的子类。ViewGroup和View以树形结构一层一层嵌套组合,就形成了视图树。当ViewTree发生变化时,会调用ViewTreeObserver的相关方法来通知这一改变。可以在ViewTreeObserver中add自己的监听器。

基本用法

1
2
3
4
5
6
7
8
9
ImageView imageView = findViewById(R.id.image_view);
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
int height = imageView.getMeasuredHeight();
int width = imageView.getMeasuredWidth();
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});

ViewTreeObserver支持的监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public final class ViewTreeObserver {
private boolean mAlive = true;

// Recursive listeners use CopyOnWriteArrayList
// 当window的焦点状态发生变化时,回调的接口类定义
private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
// 当视图树attached/detached到window时,回调的接口类定义
private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
// 当视图树的焦点状态发生变化时,回调的接口类定义
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
// 当触摸模式发生变化时,回调的接口类定义
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
private CopyOnWriteArrayList<OnEnterAnimationCompleteListener> mOnEnterAnimationCompleteListeners;

// Non-recursive listeners use CopyOnWriteArray
// Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
// 当视图树的全局布局状态发生变化或者视图树中某个view的可见状态发生变化时,回调的接口类定义
private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
// 当视图树中某些组件发生滚动时,回调的接口类定义
private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
// 当视图树将要绘制时,回调的接口类定义
private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
// 当Window展示时,回调的接口类定义
private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;

// These listeners cannot be mutated during dispatch
// 当视图树绘制时,回调的接口类定义
private ArrayList<OnDrawListener> mOnDrawListeners;
}

getViewTreeObserver

1
2
3
4
5
6
7
8
9
10
// View
public ViewTreeObserver getViewTreeObserver() {
if (mAttachInfo != null) {
return mAttachInfo.mTreeObserver;
}
if (mFloatingTreeObserver == null) {
mFloatingTreeObserver = new ViewTreeObserver(mContext);
}
return mFloatingTreeObserver;
}

AttachInfo是View的一个内部类,当一个View附着到父Window中时,它会将View和父Window之间的信息存储在AttachInfo当中。AttachInfo中有一个ViewTreeObserver对象,当mAttachInfo为空时,返回一个名为mFloatingTreeObserver的特殊的ViewTreeObserver。

接下来看看mAttachInfo的赋值流程。从Android-Window机制原理-addView可知,当调用了addView方法后,会调用到ViewRootImpl.setView方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mRootView = view;
// ...
}
// 绘制view
requestLayout();
// 由之前的解析可知Session.addToDisplay会调用到WMS.addWindow方法
// 通过session与WMS建立通信,完成window的添加
es = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
// ...
}
}

其中mAttachInfo对象在ViewRootImpl的构造方法中实例化。接着会调用requestLayout方法,这里最终会走到performTraversals方法:

1
2
3
4
5
6
7
8
9
private void performTraversals() {
final View host = mView;
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
// ...
}
// ...
}

这里的host就是之前WindowManager.addView中的View对象,考虑Activity.setContentView方法,在Android-Window机制原理中已经解析过setContentView的原理,它会创建DecorView对象,这个DecorView继承自FrameLayout,是ViewGroup子类,接着在AT.handleResumeActivity方法中会调用ViewManager.addView方法处理这个DecorView对象。即会调用到ViewGroup.dispatchAttachedToWindow方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility()));
}
}

这里会逐个调用子View的dispatchAttachedToWindow方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
// ...
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
}

void merge(ViewTreeObserver observer) {
if (observer.mOnWindowAttachListeners != null) {
if (mOnWindowAttachListeners != null) {
mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
} else {
mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
}
}
// ...
observer.kill();
}

private void kill() {
mAlive = false;
}

另外,当ViewGroup通过addView方法添加View时:

1
2
3
4
5
6
7
8
9
10
11
public void addView(View child, int index, LayoutParams params) {
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}

private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) {
// ...
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
// ...
}

于是mAttachInfo对象就在ViewTree中传开了。

添加/移除监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ViewTreeObserver
public void addOnPreDrawListener(OnPreDrawListener listener) {
checkIsAlive();
if (mOnPreDrawListeners == null) {
mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
}
mOnPreDrawListeners.add(listener);
}

public void removeOnPreDrawListener(OnPreDrawListener victim) {
checkIsAlive();
if (mOnPreDrawListeners == null) {
return;
}
mOnPreDrawListeners.remove(victim);
}

// 当调用kill方法时,会不可用
private void checkIsAlive() {
if (!mAlive) {
throw new IllegalStateException("This ViewTreeObserver is not alive, call "
+ "getViewTreeObserver() again");
}
}

分发回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final boolean dispatchOnPreDraw() {
boolean cancelDraw = false;
final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
cancelDraw |= !(access.get(i).onPreDraw());
}
} finally {
listeners.end();
}
}
return cancelDraw;
}

其它类型的监听器也是通过dispatchOnXXX方法回调的。通过Window相关原理的解析我们知道View的真正绘制流程中有个方法:ViewRootImpl.performTraversals。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
private void performTraversals() {
final View host = mView;
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
// 回调dispatchOnWindowAttachedChange方法通知视图树已经绑定到父窗口
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
// ...
}
// ...
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
mAttachInfo.mInTouchMode = !mAddedTouchMode;
// 回调dispatchOnTouchModeChanged方法通知触摸模式变化
ensureTouchModeLocally(mAddedTouchMode);
}
}
// ...
if (!mStopped || mReportNextDraw) {
// 执行View.measure过程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
// 通过dispatchOnGlobalLayout方法通知全局布局变化
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
// ...
// 通过dispatchOnPreDraw方法通知观察者绘制过程开始
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
performDraw();
} else {
if (isViewVisible) {
// 重新开始
scheduleTraversals();
}
}
}

private boolean ensureTouchModeLocally(boolean inTouchMode) {
mAttachInfo.mInTouchMode = inTouchMode;
mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
// ...
}

private void performDraw() {
boolean canUseAsync = draw(fullRedrawNeeded);
// ...
}

private void draw(boolean fullRedrawNeeded) {
// ...
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
// 通过dispatchOnScrollChanged方法通知滚动事件
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
// ...
// 通过dispatchOnDraw方法通知执行绘制
mAttachInfo.mTreeObserver.dispatchOnDraw();
// ...
}

事件分发机制

概述

Android的事件分发机制是责任链模式,如果自己能处理则拦截事件并处理,如果自己处理不了或不确定能否处理则传给下一个对象。当事件从屏幕上产生后,其分发大致经历了几个过程:屏幕->Activity->PhoneWindow->DecorView->ViewGroup->View,当这个事件没有对象去处理时,它会往回传递,如果还没有被处理,则丢弃该事件。

整个分发过程涉及到了三个重要的方法:

  1. dispatchTouchEvent(): View 和 ViewGroup 都有,将触摸事件传递给目标 View,如果自己是目标 View,则将该事件分发给自己。
  2. onInterceptTouchEvent(): 只存在于ViewGroup中,主要用于在ViewGroup拦截事件。
  3. onTouchEvent(): 处理事件的具体函数。

Activity.dispatchTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Implement this method if you wish to know that the user has
// interacted with the device in some way while your activity is running.
onUserInteraction();
}
// 传递给PhoneWindow
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 否则自己处理
return onTouchEvent(ev);
}

public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}

PhoneWindow事件处理

1
2
3
4
5
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
// 直接将事件转发给DecorView处理
return mDecor.superDispatchTouchEvent(event);
}

DecorView事件处理

1
2
3
4
public boolean superDispatchTouchEvent(MotionEvent event) {
// 直接将事件转发给ViewGroup处理
return super.dispatchTouchEvent(event);
}

ViewGroup事件处理

dispatchTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
// 判断当前window是否被遮挡,被遮挡则不处理该事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// 如果是ACTION_DOWN事件则初始化所有相关的状态值
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}

// 判断是否拦截事件
final boolean intercepted;
// 如果当前是ACTION_DOWN事件或者上一次的down事件存在子View处理
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 判断当前ViewGroup是否拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}

// 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事件并且当前ViewGroup没有拦截事件则判断是否将事件传递给子View
if (!canceled && !intercepted) {
// 无障碍?
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 childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
// ...
final View[] children = mChildren;
// 遍历子View
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// // 如果当前View可见且没有播放动画以及事件的坐标落在该View坐标范围内
// 将事件分发给当前子View,返回true表示子View消费了该事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 记录处理该事件的子View
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}

// 该事件已经有子View进行处理,则设为false
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
}
}

// 没有子View处理事件或者当前ViewGroup拦截了事件
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// 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;
// 当前是ACTION_DOWN事件并且存在子View处理该事件
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 判断是否取消事件分发给子View
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
// 将接下来的事件分发给上一次接收该ACTION_DOWN事件的子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;
}
}

// Update list of touch targets for pointer up or cancel, if needed.
// ...
}
return handled;
}

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}

dispatchTransformedTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;

// 取消事件
final int oldAction = event.getAction();
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 (newPointerIdBits == 0) {
return false;
}

// 如果不存在子View处理该事件,那么当前ViewGroup处理该事件
// 如果存在子View处理该事件,则首先调整事件发生坐标然后将该事件分发给子View处理
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
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);
}

// 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());
}

handled = child.dispatchTouchEvent(transformedEvent);
}

// Done.
transformedEvent.recycle();
return handled;
}

onInterceptTouchEvent

1
2
3
4
5
6
7
8
9
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}

小结

ViewGroup.dispatchTouchEvent 主要做了这些事情:

  1. 判断当前 View 所在 Window 是否被其它Window遮挡,如果被遮挡那么当前ViewGroup不处理该事件;
  2. 如果是ACTION_DOWN事件,首先会清除之前的所有状态;
  3. 判断当前ViewGroup是否拦截当前事件;
  4. 如果不拦截当前事件并且当前事件也不取消,则从后向前遍历ViewGroup中所有的子View,并查找到第一个满足条件的子View处理该事件;
  5. 如果存在子View处理该事件则结束查找;如果没有子View处理当前事件,那么当前ViewGroup则会处理该事件;
  6. 将ACTION_DOWN后续的事件都分发给同一个子View进行处理。如果当前ViewGroup拦截了后续的ACTION_MOVE、ACTION_UP事件,那么该事件会被取消,并且当前ViewGroup也不会处理该事件。

在dispatchTransformedTouchEvent方法中,不论是ViewGroup处理事件还是子View处理事件,最终都会调用到View中的dispatchTouchEvent方法中,即事件的最终处理都是在View中进行。

View事件处理

dispatchTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public boolean dispatchTouchEvent(MotionEvent event) {
// 无障碍服务?
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}

boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll(); // Defensive cleanup for new gesture
}

if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
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 (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}

onTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public boolean onTouchEvent(MotionEvent event) {
// ...
switch (action) {
case MotionEvent.ACTION_UP:
// ...
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
// ...
}

private boolean performClickInternal() {
notifyAutofillManagerOnClick();
return performClick();
}

public boolean performClick() {
notifyAutofillManagerOnClick();

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);

notifyEnterOrExitForAutoFillIfNeeded(true);

return result;
}

小结

在处理ACTION_UP事件时通过调用performClickInternal方法调用到performClick方法中,然后再调用用户设置的点击事件监听器。因此用户设置的listener中的onTouchEvent方法、View中的onTouchEvent方法以及设置的click事件优先级是从高到低的。如果用户设置的onTouch监听返回true,则后续的两个方法就不会执行了。

总结

  • ViewGroup处理事件时如果不拦截当前事件并且当前事件也不取消,则从后向前遍历ViewGroup中所有的子View,并查找到第一个满足条件的子View处理该事件;如果存在子View处理该事件则结束查找;如果没有子View处理当前事件,那么当前ViewGroup则会处理该事件;将ACTION_DOWN后续的事件都分发给同一个子View进行处理。如果当前ViewGroup拦截了后续的ACTION_MOVE、ACTION_UP事件,那么该事件会被取消,并且当前ViewGroup也不会处理该事件。
  • 如果View只处理down事件,而不处理接下来的其他事件,那么这些事件不会回传给ViewGroup,而是被忽略掉。因为一旦处理了down事件,那么接下来的该系列所有的事件都会交给这个View,因此,如果不处理down以外的事件,这些事件就会被丢弃。
  • 子 View 可以通过调用 ViewParent.requestDisallowInterceptTouchEvent() 方法来控制 ViewParent 对 onInterceptTouchEvent() 的调用,如果后续某个事件被拦截,子View会接收到ACTION_CANCEL事件,该事件后续事件将由拦截该事件的ViewGroup来消费。如果未拦截,则按照正常分发流程处理

滑动冲突

外部拦截法

即父View根据需要对事件进行拦截。逻辑处理放在父View的onInterceptTouchEvent方法中。我们只需要重写父View的onInterceptTouchEvent方法,并根据逻辑需要做相应的拦截即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (/*满足父View的拦截要求*/) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
return intercepted;
}
  • 根据业务逻辑在ACTION_MOVE方法中进行判断,如果需要父View处理则返回true,否则返回false,事件分发给子View去处理。
  • ACTION_DOWN 需要返回false,否则根据View事件分发机制,后续 ACTION_MOVE 与 ACTION_UP 事件都将默认交给父View去处理。
  • 理论上 ACTION_UP 也需要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也无法触发。而如果父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理。

内部拦截法

即父View不拦截任何事件,所有事件都传递给子View,子View根据需要决定是自己消费事件还是给父View处理。这需要子View使用requestDisallowInterceptTouchEvent方法才能正常工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
if (/*父View需要此类点击事件*/) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.dispatchTouchEvent(event);
}

// 父View需要重写onInterceptTouchEvent方法:
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
  • 内部拦截法要求父View不能拦截ACTION_DOWN事件,否则所有的事件都不会传递给子View。
  • 滑动策略的逻辑放在子View的dispatchTouchEvent方法的ACTION_MOVE中,如果父容器需要获取点击事件则调用 parent.requestDisallowInterceptTouchEvent(false)方法,让父容器去拦截事件。