一、整体概述
在 Android 开发中,View 作为构建用户界面的基础组件,其生命周期的管理至关重要。理解 View 生命周期的每个阶段,不仅能帮助开发者优化布局性能,还能确保在合适的时机进行资源的分配与释放。本文将从源码层面出发,对 Android View 生命周期的各个阶段进行详尽的拆解和分析。
二、实例化阶段
2.1 构造函数的调用
在创建 View 实例时,构造函数是第一个被调用的部分。View 类提供了多个构造函数重载,以满足不同的使用场景。以下是几个常见的构造函数及其源码分析:
less
public View(Context context) {
this(context, null);
}
public View(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// 保存上下文对象,后续操作会依赖该对象获取资源等
mContext = context;
mResources = context.getResources();
// 初始化基本属性
mLayoutDirection = View.LAYOUT_DIRECTION_INHERIT;
mDisplayListProperties = new DisplayListProperties();
// 解析 XML 属性
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
try {
// 处理各种属性设置,如背景、文本颜色等
mBackground = a.getDrawable(com.android.internal.R.styleable.View_background);
mTextColor = a.getColor(com.android.internal.R.styleable.View_textColor, Color.BLACK);
// 其他属性处理...
} finally {
a.recycle();
}
// 调用 init 方法进行进一步初始化
initView();
}
- 参数传递 :从简单的只接收
Context
参数的构造函数开始,逐步调用更复杂的构造函数,将AttributeSet
、defStyleAttr
和defStyleRes
等参数传递下去。 - 属性解析 :通过
TypedArray
从AttributeSet
中解析出 XML 中定义的属性。TypedArray
是一个临时数组,用于存储从资源中解析出的属性值。使用完后需要调用recycle()
方法回收,以避免内存泄漏。 - 初始化操作 :
initView()
方法通常用于执行一些额外的初始化逻辑,例如设置默认值、初始化监听器等。
2.2 onFinishInflate 方法
当 View 从 XML 布局文件中加载完成后,会调用 onFinishInflate()
方法。该方法在 View
类中是一个空实现,主要用于开发者在自定义 View 时进行扩展。
scss
protected void onFinishInflate() {
super.onFinishInflate();
// 在这里可以进行一些初始化操作,比如获取子 View 引用
TextView textView = findViewById(R.id.text_view);
if (textView != null) {
textView.setText("Initial Text");
}
}
- 调用时机:在 XML 布局文件解析完成,所有子 View 都已经创建并添加到当前 View 中后调用。
- 应用场景:可以在该方法中进行一些依赖于子 View 的初始化操作,例如设置子 View 的属性、添加监听器等。
三、测量阶段
3.1 measure 方法的调用流程
测量阶段的入口是 measure(int widthMeasureSpec, int heightMeasureSpec)
方法,该方法由父 View 调用,用于测量子 View 的大小。
ini
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// 生成一个唯一的 key,用于测量结果的缓存
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
// 判断是否需要重新测量
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// 清除测量维度标志
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
// 解析 RTL 属性
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// 调用 onMeasure 方法进行测量
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
// 从缓存中获取测量结果
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 检查是否设置了测量维度
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
// 将测量结果存入缓存
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL);
}
- 光学布局调整 :通过
isLayoutModeOptical
方法判断是否需要进行光学布局调整,如果需要则调用MeasureSpec.adjust
方法对测量规格进行调整。 - 缓存机制 :使用
LongSparseLongArray
作为测量结果的缓存,通过生成的唯一key
进行查找。如果缓存中存在对应的测量结果,则直接使用,避免重复测量。 - 重新测量判断 :如果设置了
PFLAG_FORCE_LAYOUT
标志,或者测量规格发生了变化,则需要重新测量。 - 调用
onMeasure
方法 :如果需要重新测量,则调用onMeasure
方法进行实际的测量操作。
3.2 MeasureSpec 详解
MeasureSpec
是一个 32 位的整数,高 2 位表示测量模式,低 30 位表示测量大小。测量模式有三种:
-
MeasureSpec.UNSPECIFIED:父容器不对 View 施加任何约束,View 可以任意大小。通常用于系统内部的一些测量场景。
-
MeasureSpec.AT_MOST :View 的大小不能超过父容器指定的最大值。对应 XML 中的
wrap_content
属性。 -
MeasureSpec.EXACTLY :父容器已经确定了 View 的精确大小。对应 XML 中的
match_parent
或具体的尺寸值。
以下是 MeasureSpec
相关的一些方法源码:
java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* 测量模式:未指定
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* 测量模式:最多
*/
public static final int AT_MOST = 1 << MODE_SHIFT;
/**
* 测量模式:精确
*/
public static final int EXACTLY = 2 << MODE_SHIFT;
/**
* 根据大小和模式创建一个 MeasureSpec
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* 获取 MeasureSpec 中的测量模式
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* 获取 MeasureSpec 中的测量大小
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
makeMeasureSpec
方法 :用于根据测量大小和模式创建一个MeasureSpec
。getMode
方法 :从MeasureSpec
中提取测量模式。getSize
方法 :从MeasureSpec
中提取测量大小。
3.3 onMeasure 方法的实现
onMeasure
方法是 View 测量阶段的核心方法,需要在自定义 View 时重写该方法来确定 View 的大小。
java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
getDefaultSize
方法:根据测量模式和建议的最小尺寸计算最终的尺寸。getSuggestedMinimumWidth
和getSuggestedMinimumHeight
方法:获取 View 的建议最小宽度和高度,考虑了背景的最小尺寸。setMeasuredDimension
方法 :设置测量得到的宽度和高度,必须在onMeasure
方法中调用,否则会抛出异常。
四、布局阶段
4.1 layout 方法的执行流程
测量完成后,会进入布局阶段,布局阶段的入口是 layout(int l, int t, int r, int b)
方法。
ini
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
- 重新测量检查 :如果设置了
PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
标志,则重新调用onMeasure
方法进行测量。 - 设置 View 的位置 :通过
setFrame
或setOpticalFrame
方法设置 View 的位置和大小。如果位置或大小发生了变化,changed
标志会被设置为true
。 - 调用
onLayout
方法 :如果位置或大小发生了变化,或者设置了PFLAG_LAYOUT_REQUIRED
标志,则调用onLayout
方法进行布局操作。 - 布局变化监听器 :如果注册了
OnLayoutChangeListener
,则在布局变化时会调用监听器的onLayoutChange
方法。
4.2 onLayout 方法的实现
onLayout
方法用于确定子 View 的位置,对于 ViewGroup
来说,需要重写这个方法来布局子 View。
arduino
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 空实现,需要在 ViewGroup 子类中重写
}
在 ViewGroup
的子类中,通常会遍历子 View 并调用它们的 layout
方法来确定每个子 View 的位置。例如,LinearLayout
的 onLayout
方法实现如下:
ini
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// 计算子 View 的位置
final int width = right - left;
int childRight = width - mPaddingRight;
// 遍历子 View
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
childLeft = paddingLeft + getLocationOffset(child);
childTop = calculateChildTop(i, childHeight);
// 调用子 View 的 layout 方法
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
}
}
}
- 布局方向判断 :根据
mOrientation
的值判断是垂直布局还是水平布局,然后调用相应的布局方法。 - 子 View 位置计算:根据子 View 的测量大小和布局规则,计算每个子 View 的位置。
- 调用子 View 的
layout
方法 :将计算得到的位置传递给子 View 的layout
方法,完成子 View 的布局。
五、绘制阶段
5.1 draw 方法的绘制流程
绘制阶段的入口是 draw(Canvas canvas)
方法,该方法用于绘制 View 的内容。
java
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step
接着draw 方法的绘制流程继续深入分析一下
五、绘制阶段(续)
5.1 draw 方法的绘制流程(深入分析)
1. 绘制背景(Draw the background)
java
scss
if (!dirtyOpaque) {
drawBackground(canvas);
}
在 draw
方法中,首先判断 dirtyOpaque
标志。如果 dirtyOpaque
为 false
,则调用 drawBackground(canvas)
方法绘制背景。dirtyOpaque
用于标记视图是否为不透明的脏区域,如果是不透明且脏区域,可能不需要重新绘制背景以节省性能。
drawBackground
方法的源码如下:
ini
rivate void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// 检查是否需要设置透明度
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
- 首先检查背景
Drawable
是否为空,如果为空则直接返回。 - 调用
setBackgroundBounds()
方法设置背景的边界,使其与视图的边界匹配。 - 根据视图的滚动状态(
mScrollX
和mScrollY
),如果视图没有滚动,则直接在Canvas
上绘制背景;如果有滚动,需要先将Canvas
进行平移,绘制背景后再将Canvas
平移回来。
2. 保存画布状态(Save the canvas' layers if necessary)
在某些情况下,需要保存画布的状态以准备渐变效果等。这部分代码通常在满足特定条件(如存在渐变边缘)时执行。
ini
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (verticalEdges || horizontalEdges) {
// 保存画布状态
saveCount = canvas.getSaveCount();
canvas.saveLayer(left, top, right, bottom, null, Canvas.ALL_SAVE_FLAG);
}
- 通过
viewFlags
检查是否存在水平或垂直的渐变边缘。 - 如果存在渐变边缘,调用
canvas.saveLayer()
方法保存画布的状态,该方法会创建一个新的图层,后续的绘制操作会在这个新图层上进行。
3. 绘制视图内容(Draw view's content)
scss
if (!dirtyOpaque) {
onDraw(canvas);
}
同样,先判断 dirtyOpaque
标志。如果视图不是不透明的脏区域,则调用 onDraw(canvas)
方法绘制视图的内容。onDraw
方法是一个空实现,需要在自定义视图时重写该方法来绘制具体的内容,例如绘制图形、文本等。
scss
protected void onDraw(Canvas canvas) {
// 自定义绘制逻辑
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 50, paint);
}
在这个示例中,我们在视图的中心绘制了一个红色的圆形。
4. 绘制子视图(Draw children)
scss
dispatchDraw(canvas);
dispatchDraw
方法用于绘制子视图。在 View
类中,dispatchDraw
方法是一个空实现,因为 View
本身没有子视图。而在 ViewGroup
类中,会重写该方法来遍历并绘制所有的子视图。
java
@Override
protected void dispatchDraw(Canvas canvas) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
dispatchDraw
方法会遍历所有的子视图,检查子视图的可见性和动画状态。如果子视图可见或者正在执行动画,则调用drawChild
方法绘制该子视图。drawChild
方法会调用子视图的draw
方法,从而递归地完成整个视图树的绘制。
5. 绘制渐变边缘并恢复图层(Draw the fading edges and restore layers if necessary)
如果之前保存了画布的图层,在绘制完子视图后,需要绘制渐变边缘并恢复图层。
scss
if (verticalEdges || horizontalEdges) {
// 绘制渐变边缘
if (horizontalEdges) {
drawHorizontalFadingEdge(canvas, top, bottom);
}
if (verticalEdges) {
drawVerticalFadingEdge(canvas, left, right);
}
// 恢复画布状态
canvas.restoreToCount(saveCount);
}
- 根据是否存在水平或垂直的渐变边缘,调用相应的方法绘制渐变边缘。
- 调用
canvas.restoreToCount(saveCount)
方法恢复之前保存的画布状态,将绘制操作切换回原来的图层。
6. 绘制装饰(Draw decorations)
scss
onDrawForeground(canvas);
最后,调用 onDrawForeground(canvas)
方法绘制视图的装饰,如滚动条等。onDrawForeground
方法会先绘制前景 Drawable
,然后绘制滚动条等装饰元素。
scss
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForeground;
if (foreground != null) {
if (mForegroundInfo != null) {
mForegroundInfo.mBounds.set(0, 0, getWidth(), getHeight());
foreground.setBounds(mForegroundInfo.mBounds);
}
foreground.draw(canvas);
}
}
- 先调用
onDrawScrollIndicators(canvas)
和onDrawScrollBars(canvas)
方法绘制滚动指示器和滚动条。 - 如果存在前景
Drawable
,则设置其边界并绘制在Canvas
上。
5.2 硬件加速与绘制优化
在 Android 中,硬件加速可以显著提高绘制性能。当启用硬件加速时,Canvas
的绘制操作会由 GPU 来处理。
硬件加速的开启方式
可以在 AndroidManifest.xml
中为整个应用、某个 Activity
或特定的 View
开启硬件加速。
ini
<application
android:hardwareAccelerated="true"
... >
...
</application>
绘制优化技巧
- 减少不必要的重绘 :通过合理设置视图的
invalidate
和requestLayout
方法,避免不必要的重绘操作。例如,只在视图的状态发生变化时调用invalidate
方法。 - 使用
Canvas
的裁剪和变换 :通过Canvas
的裁剪和变换方法,可以减少不必要的绘制区域,提高绘制效率。例如,使用canvas.clipRect()
方法裁剪绘制区域。 - 避免在
onDraw
方法中创建对象 :onDraw
方法会频繁调用,在其中创建对象会导致频繁的垃圾回收,影响性能。可以将对象的创建移到onCreate
或onFinishInflate
等方法中。
六、事件处理阶段
6.1 事件分发机制概述
当用户触摸屏幕时,触摸事件会从顶层的 View
开始向下传递,经过一系列的 View
或 ViewGroup
,最终到达目标 View
。事件分发机制主要涉及三个方法:dispatchTouchEvent
、onInterceptTouchEvent
和 onTouchEvent
。
6.2 dispatchTouchEvent 方法
dispatchTouchEvent
方法是事件分发的入口,用于将触摸事件分发给子视图或自身处理。
csharp
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
return true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
- 首先检查输入事件的一致性验证器
mInputEventConsistencyVerifier
,确保事件的合法性。 - 调用
onFilterTouchEventForSecurity
方法过滤不安全的事件。 - 如果视图是启用状态且正在处理滚动条拖动事件,则直接返回
true
,表示事件已处理。 - 检查是否设置了
OnTouchListener
,如果设置了且OnTouchListener
的onTouch
方法返回true
,则表示事件已被处理,返回true
。 - 调用
onTouchEvent
方法处理事件,如果onTouchEvent
方法返回true
,则表示事件已被处理,返回true
。 - 如果以上条件都不满足,则返回
false
,表示事件未被处理,需要继续向上传递。
6.3 onInterceptTouchEvent 方法(仅适用于 ViewGroup)
onInterceptTouchEvent
方法是 ViewGroup
特有的方法,用于拦截触摸事件,阻止事件继续向下传递给子视图。
scss
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;
}
在默认情况下,onInterceptTouchEvent
方法返回 false
,表示不拦截事件。可以在自定义的 ViewGroup
中重写该方法,根据需要拦截事件。
6.4 onTouchEvent 方法
onTouchEvent
方法用于处理触摸事件,例如点击、长按、滑动等。
scss
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_DOWN:
// 处理按下事件
setPressed(true);
checkForLongClick(0, x, y);
break;
case MotionEvent.ACTION_UP:
// 处理抬起事件
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 移除长按检测
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
mIgnoreNextUpEvent = false;
}
break;
case MotionEvent.ACTION_CANCEL:
// 处理取消事件
setPressed(false);
removeLongPressCallback();
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
// 处理移动事件
drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) {
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
- 首先检查视图是否禁用,如果禁用则根据点击状态进行相应处理。
- 检查是否设置了触摸代理
mTouchDelegate
,如果设置了且触摸代理处理了事件,则返回true
。 - 如果视图是可点击、可长按或可上下文点击的,则根据触摸事件的类型(按下、抬起、取消、移动)进行相应的处理。
- 例如,在按下事件中,设置视图为按下状态并开始长按检测;在抬起事件中,检查是否触发了点击事件并执行相应的操作。
6.5 事件处理流程总结
- 触摸事件从顶层的
View
开始,调用dispatchTouchEvent
方法进行事件分发。 - 如果是
ViewGroup
,会先调用onInterceptTouchEvent
方法判断是否拦截事件。如果拦截,则事件由该ViewGroup
处理;如果不拦截,则继续向下传递给子视图。 - 子视图调用
dispatchTouchEvent
方法,重复上述步骤,直到找到目标View
。 - 目标
View
调用onTouchEvent
方法处理事件,如果处理成功则返回true
,表示事件已处理;如果处理失败则返回false
,事件会向上传递给父视图处理
七、销毁阶段
7.1 onDetachedFromWindow 方法
当 View
从窗口中分离时,会调用 onDetachedFromWindow
方法。该方法通常用于释放资源、注销监听器等操作,以避免内存泄漏。
typescript
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// 释放资源
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
}
// 注销监听器
if (mMyListener != null) {
mSomeManager.unregisterListener(mMyListener);
mMyListener = null;
}
}
- 在
onDetachedFromWindow
方法中,首先调用父类的onDetachedFromWindow
方法。 - 释放
View
持有的资源,例如动画对象、定时器等。 - 注销之前注册的监听器,避免在
View
销毁后仍然接收事件。