一、View绘制流程机制
1、View绘制起点
-
performTraversals()
方法触发了View 的绘制。
说明:
在Activity显示时,WindowManager
将View添加到DecorView
,两者通过ViewRoot
连接起来。
具体实现类是ViewRootImpl
。
再通过ViewRootImpl
的一系列处理,最终调用performTraversals
方法,在performTraversals
方法中,依次调用了performMeasure()
,performLayout()
,performDraw()
,将View 的measure
,layout,
draw` 过程从顶层View 分发了下去。开始了View的绘制。
2、View绘制流程
- 绘制过程分为三步:
measure
(测量) -->layout
(布局) -->draw
(绘制),
在draw
流程结束以后就可以在屏幕上看到view了。 -
流程图如下:
.1、measure
(测量)
测量的目的,是为了计算出View的大小,通过
MeasureSpec
来进行计算的。
MeasureSpec
是一个specSize
和specMode
信息的32 位int 值,其中高两位表示specMode
,低30位表示specSize
。specMode
模式:
UNSPECIFIED
:父容器不对View有任何限制,要多大有多大。常用于系统内部。
EXACTLY
(精确模式):父视图为子视图指定一个确切的尺寸SpecSize。对应LyaoutParams中的match_parent或具体数值。
AT_MOST
(最大[限制]模式):父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。-
决定View大小的因素:
因素:widthMeasureSpec
和heightMeasureSpec
MeasureSpec
值由 子View的布局参数LayoutParams 和 父容器的MeasureSpec值 共同决定。具体规则见下图:
顶级View(即DecorView)的测量在ViewRootImpl的源码里(getRootMeasureSpec
方法):
//desire的这2个参数就代表屏幕的宽高,
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//decorView的measureSpec就是在这里确定的,其实比普通view的measurespec要简单的多
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
- 流程:
performMeasure() 会调用 measure()(final方法),将计算的MeasureSpec
传递给调用的onMeasure()
方法,此方法会调用setMeasuredDimension()
来设置自身大小;
如果是ViewGroup,先遍历子View,测量出子View的MeasureSpec
,再调用measureChild*
相关方法让子View通过调用measure方法来测量自己的大小;再根据所有子View的大小确定自身的大小,其中的子View会重复此类的measure过程,如此反复至完成整个View树的遍历,确定各个View自身的大小。
注意:
- ViewGroup中没有
onMeasure
方法- View会进行多次的测量,第一次测量和最终测量的实际宽高不一定相等,在layout流程中可确定View的实际宽高
- 获取measure()后的宽高方法:
- Activity#onWindowFocusChange()中获取
- view.post(Runnable)将获取的代码投递到消息队列的尾部
- ViewTreeObservable方法
.2、layout
(布局)
布局目的:确定View的 最终宽高 和 四个顶点的位置
-
流程:
performLayout()
会调用顶级View的layout()
方法,其中调用setFrame()
方法来设置其四个顶点(mLeft、mRight、mTop、mBottom);
接着调用onLayout()
(空方法),此方法由具体实现的View自身重写,用来确定自身位置,及循环其子View来确定坐标位置,子View 会循环调用setChildFrame()
(就是调用View.layout()
)。
layout和onLayout方法有什么区别?
layout是确定本身view的位置,通过serFrame方法设定本身view的四个顶点的位置。
onLayout是确定所有子元素的位置。
View和ViewGroup的 onLayout 方法都是空方法。都留给我们自己给子元素布局。
.3、draw
(绘制)
- 绘制目的:显示View
- 流程:
performMeasure()
会调用 ViewRootImpl的draw方法,再调用drawSoftWare()
方法,其中会调用mView.draw()
,是真正绘制步骤的开始。绘制步骤如下:(1、3、4、6四步为主要步骤)
- 1、Draw the background:绘制背景 ——
drawBackground(canvas)
- If necessary, save the canvas' layers to prepare for fading:保存图层
- Draw view's content:绘制view自身的内容 ——
onDraw(canvas)
- Draw children:绘制子View(分发) ——
dispatchDraw(canvas)
- If necessary, draw the fading edges and restore layers:绘制一些图层
- Draw decorations (scrollbars for instance):绘制装饰(如滚动条) ——
onDrawForeground(canvas)
注:View中的dispatchDraw(canvas)
是空方法,ViewGroup中的dispatchDraw(canvas)
调用了其drawChild
方法
-
绘制流程图:
说明:
setWillNotDraw
:用于设置绘制的标志位,view是否需要draw绘制。
若自定义的View 不需要draw,则可以设置这个方法为true。系统会根据此标记来优化执行速度。
ViewGroup 一般都默认设置这个为true,因为ViewGroup多数都是只负责布局,不负责draw的。
View 的这个标志位默认一般都是关闭的。
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
- 一般执行动画,会多次调用onDraw方法,通过监听动画的参数值变化,不断
invalidate
,不断重绘。
invalidate
是在 主线程 中进行调用,会引发onDraw进行重绘
postInvalidate
是在 子线程 中调用,最终调用的仍是invalidate
参考链接:
Android View绘制13问13答
要点提炼|开发艺术之View
二、View及ViewGroup的事件机制
1、相关概念:
- MotionEvent事件
- 事件类型:
ACTION_DOWN
:手指刚接触屏幕,按下去的那一瞬间产生该事件
ACTION_MOVE
:手指在屏幕上移动时候产生该事件
ACTION_UP
:手指从屏幕上松开的瞬间产生该事件- 事件序列:
从ACTION_DOWN开始到ACTION_UP结束我们称为一个事件序列
- TouchSlop
系统所能识别的被认为是滑动的最小距离。
即当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。
该常量和设备有关,可用它来判断用户的滑动是否达到阈值,获取方法:
ViewConfiguration.get(getContext()).getScaledTouchSlop()
VelocityTracker
速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。GestureDetector
手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。
2、事件分发的要点
事件分发的本质:
是对MotionEvent事件分发的过程,并将事件消费处理。事件分发的传递顺序:
Activity(Window) --> ViewGroup --> View
即最终调用的是 View的dispatchTouchEvent(MotionEvent event)
方法事件分发的重要方法:
1、dispatchTouchEvent(MotionEvent event)
:分发事件,返回boolean类型,表示事件是否消费
2、onInterceptTouchEvent(MotionEvent event)
:中断事件(仅ViewGroup有),返回boolean类型,表示事件是否中断
3、onTouchEvent(MotionEvent event)
:消费事件(仅View有),返回boolean类型,表示事件是否消费
4、perform*Click()
:执行事件(仅View有),最终是onClick(View view),或长按、双击等事件处理。事件分发伪代码:
// ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {
// 事件是否被消费
boolean consume = false;
// 调用onInterceptTouchEvent判断是否拦截事件
if (onInterceptTouchEvent(ev)) {
// 如果拦截则调用自身的onTouchEvent方法
consume = onTouchEvent(ev);
} else {
if (targetChild == null) {
// 没有找到目标child,则调用父容器的分发方法
consume = super.dispatchTouchEvent(ev);
} else {
// 不拦截调用子View的dispatchTouchEvent方法
consume = child.dispatchTouchEvent(ev);
}
}
// 返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
return consume;
}
// View
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
// 是否实现了TouchListener#onTouch方法
if (onTouchListener != null) {
// 调用实现的onTouchListener#onTouch方法
consume = onTouchListener.onTouch(ev);
} else {
// onTouchEvent()中调用了perform*Click()等方法
consume = onTouchEvent(ev);
}
//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
return consume;
}
3、事件分发的机制&流程
-
示意图
流程详述:
- 1、ViewGroup分发开端:
Acivity#dispatchTouchEvent(MotionEvent)
事件最开始从Activity开始,由Acivity的dispatchTouchEvent方法来对事件进行分发。
// Activity源码:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 事件分发并返回结果
if (getWindow().superDispatchTouchEvent(ev)) {
//事件被消费
return true;
}
// 无View 消费事件,则调用Activity#onTouchEvent方法
return onTouchEvent(ev);
}
PhoneWindow#superDispatchTouchEvent(MotionEvent)
getWindow().superDispatchTouchEvent(ev)
是Window
的抽象方法,具体由PhoneWindow
实现
其内部是调用的顶级View(DecorView)的superDispatchTouchEvent
方法
// PhoneWindow源码:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView.superDispatchTouchEvent(MotionEvent)
顶级View(DecorView)一般为ViewGroup,其方法中调用了ViewGroup#dispatchTouchEvent
方法
public boolean superDispatchTouchEvent(MotionEvent event) {
// 此处调用的是ViewGroup的dispatchTouchEvent方法
return super.dispatchTouchEvent(event);
}
- 2、ViewGroup事件分发 —
onInterceptTouchEvent
:
MotionEvent.ACTION_DOWN
事件:
先判断是否为MotionEvent.ACTION_DOWN
事件,是,则清除FLAG_DISALLOW_INTERCEPT
设置并且mFirstTouchTarget 设置为null,然后根据条件调用onInterceptTouchEvent
方法,来处理拦截事件。
如果不是MotionEvent.ACTION_DOWN
事件,且mFirstTouchTarget == null
,则直接设置中断标记为true(intercepted = true),ViewGroup直接拦截其他事件(如MOVE和UP等)进行处理。
FLAG_DISALLOW_INTERCEPT
标志位:
如果通过requestDisallowInterceptTouchEvent
方法设置了此标志位,则子View可以以此来干预父View的事件分发过程(ACTION_DOWN事件除外,上面的原因),而这就是我们处理滑动冲突常用的关键方法。
// ViewGroup源码:
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) {
// 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);
//清除FLAG_DISALLOW_INTERCEPT设置并且mFirstTouchTarget 设置为null
resetTouchState();
}
// Check for interception.
final boolean intercepted;//是否拦截事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT是子View通过
//requestDisallowInterceptTouchEvent方法进行设置的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用onInterceptTouchEvent方法判断是否需要拦截
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;
}
...
}
-
3、ViewGroup事件分发 — 子View遍历
mFirstTouchTarget
:
当ViewGroup不拦截事件,则遍历子View,找到事件接收的目标View(mFirstTouchTarget
),条件:- View可见且没有播放动画:
canViewReceivePointerEvents
方法 - 事件的坐标落在View的范围内:
isTransformedTouchPointInView
当mFirstTouchTarget不为null,则说明已经找到过了目标child,则newTouchTarget不为null,会跳出循环。
但此时还没有将事件分发给子View,所以newTouchTarget为null,mFirstTouchTarget也是null。
如果dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法
返回了true,即子View消费了事件,则会将mFirstTouchTarget进行赋值为该子View,终止子View的遍历。此时,子View的遍历完成。
在dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法
中,如果子View不为null,则调用了子View的child.dispatchTouchEvent
分发方法,进行View的分发。 - View可见且没有播放动画:
// ViewGroup源码:
public boolean dispatchTouchEvent(MotionEvent ev) {
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 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;
}
//判断1,View可见并且没有播放动画。2,点击事件的坐标落在View的范围内
//如果上述两个条件有一项不满足则continue继续循环下一个View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
//如果有子View处理即newTouchTarget 不为null则跳出循环。
if (newTouchTarget != null) {
// 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第三个参数child这里不为null
//实际调用的是child的dispatchTouchEvent方法
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();
//当child处理了点击事件,那么会设置mFirstTouchTarget 在addTouchTarget被赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//子View处理了事件,然后就跳出了for循环
break;
}
}
}
dispatchTransformedTouchEvent
方法:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
mFirstTouchTarget
的赋值:
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
- 4、ViewGroup事件分发 —
View#dispatchTouchEvent
:
若遍历子View后,ViewGroup没有找到事件处理者(① ViewGroup没有子View 或 ② 子View处理了事件却在dispatchTouchEvent方法返回了false),则ViewGroup会去处理这个事件。
即dispatchTouchEvent
方法返回了false,mFirstTouchTarget 必然为null,则再次调用自身的dispatchTransformedTouchEvent
方法(但传入的child为null),其内部会调用super.dispatchTouchEvent(event);
方法,将调用View#dispatchTouchEvent
,将事件传给View,至此,ViewGroup的分发过程完成。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
- 5、View的事件分发 —
View#dispatchTouchEvent
mOnTouchListener.onTouch
监听:
View中首先判断是否设置了OnTouchListener
监听(开发者自己实现的),若设置了且onTouch
返回true
,则之后的onTouchEvent
方法不会调用;若没有设置监听或onTouch
返回false
,则会调用onTouchEvent
方法。
onTouchEvent
方法:
在此方法中,具体的处理了各个事件。
如果View设置成了disabled
状态(即不可用),只要CLICKABLE
和LONG_CLICKABLE
有一个为true
,就一定会消费这个事件(即onTouchEvent
返回true),只是它看起来不可用。只有不可点击(clickable
和longClickable
同时为false
),才会返回false,即onTouchEvent不消费此事件。
performClick()
方法:
就点击事件而言,在ACTION_UP
事件的条件下,会调用performClickInternal
方法(内部实际是performClick()
);在performClick()
方法中,如果设置了OnClickListener
,则会回调onClick
方法。
dispatchTouchEvent(MotionEvent)
// View源码:
//如果窗口没有被遮盖
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
//当前监听事件
ListenerInfo li = mListenerInfo;
//需要特别注意这个判断当中的li.mOnTouchListener.onTouch(this, event)条件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//result为false调用自己的onTouchEvent方法处理
if (!result && onTouchEvent(event)) {
result = true;
}
}
onTouchEvent(MotionEvent)
// 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;
// 判断是否不可用,但仍会消费事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// 此处重点
// 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 (!post(mPerformClick)) {
// 其内部调用的是performClick方法
performClickInternal();
}
...
break;
...
}
return true;
}
return false;
}
performClick()
// View源码:
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
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;
}
说明: setClickable
失效的原因:
View的setOnClickListener
会默认将View的clickable设置成true。
View的setOnLongClickListener
同样会将View的longClickable设置成true。
// View源码:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
4、事件分发的相关问题:
-- 问题:如果一个事件序列的 ACTION_DOWN 事件被 ViewGroup 拦截,此时子 View 调用 requestDisallowInterceptTouchEvent
方法有没有用?
子View可以通过 requestDisallowInterceptTouchEvent
方法干预父View的事件分发过程(ACTION_DOWN事件除外),而这就是我们处理滑动冲突常用的关键方法。
在
requestDisallowInterceptTouchEvent
中,设置了FLAG_DISALLOW_INTERCEPT
标志位,表示子View不希望此父级及其祖先使用ViewGroup.onInterceptTouchEvent(MotionEvent)
拦截触摸事件。
而 ACTION_DOWN
事件是清除了这个标志位的,所以,requestDisallowInterceptTouchEvent
的设置对ACTION_DOWN无效。
// ViewGroup源码,实现的是ViewParent的抽象方法
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
// ViewGroup#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
...
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
....
//清除FLAG_DISALLOW_INTERCEPT设置并且mFirstTouchTarget 设置为null
resetTouchState();
}
//是否拦截事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT是子View通过
//requestDisallowInterceptTouchEvent方法进行设置的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用onInterceptTouchEvent方法判断是否需要拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
...
}
-- 问题:ACTION_DOWN
事件被子 View
消费了,那 ViewGroup
能拦截剩下的事件吗?如果拦截了剩下事件,当前这个事件 ViewGroup 能消费吗?子 View 还会收到事件吗?
在ACTION_DOWN
事件被子 View
消费后,mFirstTouchTarget
则不为null了,就会直接拦截其他事件,intercepted = true;
,见下面的源码。
设置了拦截,就不会再遍历子View进行事件分发了。则会取消cancelChild
,拦截子View的事件。
// ViewGroup#dispatchTouchEvent
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// onInterceptTouchEvent方法的判断
......
} else {
// 重点在这里
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true; // <---------------------------------------重点
}
....
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 没有子 View 消费事件,则传入 null 去分发,最终调用的是自身的 onTouchEvent 方法,进行处理 touch 事件
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;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 如果 intercepted 就取消 cancelChild,这便是拦截子 View 事件的原理
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted; // <---------------------------------------重点
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
//内部会比较 pointerIdBits 和当前事件的 pointerIdBits,一致才会处理
//这便是 Down 事件处理后后续事件都交给该 View 处理的原理
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
// 没有next则为null,就结束了循环
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
-- 问题:当 View Disable 时,会消费事件吗?
会消费事件,只是设为了不可用,可以看到,在源码中的注释为:
A disabled view that is clickable still consumes the touch events, it just doesn't respond to them.
[一个可点击的禁用view,仍然可以消费事件,它只是没有响应它们(事件)而已。]
// View#onTouchEvent
// 判断是否不可用,但仍会消费事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// 此处重点
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
参考链接:
一文读懂Android View事件分发机制
必问的事件分发,你答得上来吗
Android事件分发机制详解:史上最全面、最易懂
要点提炼|开发艺术之View