文章主要参考书籍《Android 开发艺术探索》(任玉刚 著),与书籍主要区别:
- 源代码基于 Android 19,Android 版本变化导致的变化会标注说明
- 追加大量流程图,源代码追加更详细注释,以方便理解与记忆
- 扩展了一些书籍上未说明的源代码、流程说明
前言
View 的三大流程:测量流程(mesure)、布局流程(layout)、绘制流程(draw)
目录
- 一、初识 ViewRootImpl 和 PhoneWindow.DecorView
- 二、理解 View.MeasureSpec
- 小总结
- MeasureSpec 格式
- MeasureSpec 和 ViewGroup.LayoutParams 关系
- MeasureSpec 计算的流程与结果
- DecorView 的 MeasureSpec 计算源码分析
- View 的 MeasureSpec 计算源码分析
- 三、View 的 measure 过程
- 小总结
- 相关 API
- 从 DecorView 到 View 的 measure 原理分析
- ViewGroup 的 measure 原理分析
- View 的 measure 原理分析
- 在 Activity 生命周期中获取测量结果解决方案
- 四、View 的 layout 过程
- 小总结
- 相关 API
- 从 DecorView 到 View 的 layout 原理分析
- View 的 layout 原理分析
- 五、View 的 draw 过程
- 小总结
- 相关 API
- 从 DecorView 到 View 的 draw 原理分析
- View 的 draw 原理分析
- ViewGroup 的 draw 原理分析
一、初识 ViewRootImpl 和 PhoneWindow.DecorView
DecorView 作为顶级 View,它继承自 FrameLayout,View 层的事件都记过 DecorView,然后才传递给 View
ViewRootImpl:View 阶级的最高级,View 的三大流程均由它实现,是连接 WindowManager 和 DecorView 的纽带,主要实现 View 与 WindowManager 之间的协议,可参考 WindowManagerGlobal。ViewRootImpl官方释义如下:
/** * The top of a view hierarchy, implementing the needed protocol between View * and the WindowManager. This is for the most part an internal implementation * detail of {@link WindowManagerGlobal}. */
public final class ViewRootImpl implements ...
ActivityThread 中,当 Activity 创建完毕后,会将 DecorView 添加到 Window 中,同时创建 ViewRootImpl 对象,并将 ViewRootImpl 与 DecorView 建立关联
/** * WindowManagerGlobal 类 */
public void addView(View, ViewGroup.LayoutParams, Display, Window) {
...
root = new ViewRootImpl(view.getContext(), display);
...
root.setView(view, wparams, panelParentView);
...
}
View 绘制从 ViewRootImpl.performTraversals() 开始,依次完成测量流程(mesure)、布局流程(layout)、绘制流程(draw)。流程图如下:
/** * ViewRootImpl 类 */
private void performTraversals() {
/** * 参数说明 * mWidth, mHeight: 窗口有效尺寸 * lp: 窗口布局参数 * desiredWindowWidth: 窗口尺寸 * desiredWindowHeight: 窗口尺寸 */
...
// 获取根 View 的 MeasureSpec,并执行测量流程
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
// 布局流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
// 绘制流程
performDraw();
...
}
- 测量过程[performMeasure()]完成后,View 高宽就测量完毕,在几乎所有情况下,它都等于 View 的最终高宽
- 布局过程[performLayout()]决定了 View 的四个顶点的坐标,以及 View 的实际高宽
- 绘制过程[performDraw()]决定了 View 的内容显示
二、理解 View.MeasureSpec
小总结
- MeasureSpec 是 Android 系统用来测量 View 的高宽的参数
- 对于 DecorView,其 MeasureSpec 由窗口的尺寸和其自身的 LayoutParams 共同确定
- 对于普通 View,其 MeasureSpec 有父容器的 MeasureSpec 和自身 LayoutParams 共同确定
1. 相关 API
获取计算结果:
- int getMode(int)
- int getSize(int)
2. MeasureSpec 格式
MeasureSpec 是32位 int 值,高2位表示 SpecMode,低30位表示 SpecSize
- SpecMode:指测量模式,有三类:UNSPECIFIED、EXACTLY、AT_MOST
- SpecSize:值在某个测量模式下的规格大小
- UNSPECIFIED:未注明的,意思是父容器对 View 不做任何限制,要多大给多大,一般用于系统内部
- EXACTLY:精确的,意思是明确 View 大小值,对应 LayoutParams.MATCH_PARENT 和具体数值这两种模式
- AT_MOST:至多的,意思是 View 的大小值根据具体实现,但不能超过该值,对应 LayoutParams.WRAP_CONTENT
/** * 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 EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
3. MeasureSpec 和 ViewGroup.LayoutParams 关系
在 View 测量的时候,系统会将 View 的 LayoutParams 根据***父容器所施加的约束***转换成对应的 MeasureSpec,然后***根据这个 MeasureSpec 来测量 View 的高宽***
4. MeasureSpec 计算的流程与结果
DecorView、View 的 MeasureSpec 计算流程,分别如下:
DecorView、View 的 MeasureSpec 计算结果表,分别如下:
5. DecorView 的 MeasureSpec 源码分析
/** * ViewRootImpl 类 */
/** * @param lp WindowManager.LayoutParams, 窗口布局参数 * @param desiredWindowWidth int, 窗口尺寸 * @param desiredWindowHeight int, 窗口尺寸 */
private boolean measureHierarchy(...) {
...
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
/** * @param windowSize 窗口有效尺寸 * @param rootDimension 窗口的布局参数尺寸 * @return 根 View(DecorView) 的 MeasureSpec */
/** * 官方释义 * @param windowSize The available width or height of the window * @param rootDimension The layout params for one dimension (width or height) of the window * @return The measure spec to use to measure the root view */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
6. View 的 MeasureSpec 计算源码分析
/**
* ViewGroup 类
*/
/**
* @param child 子 View
* @param parentWidthMeasureSpec 父容器宽度
* @param widthUsed 额外使用尺寸
* @param parentHeightMeasureSpec 父容器高度
* @param heightUsed 额外使用尺寸
*/
protected void measureChildWithMargins(View, int, int, int, int) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(
parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed,
lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(
parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed,
lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/**
* @param spec 父容器尺寸
* @param padding 补白区尺寸
* @param childDimension View 尺寸
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
三、View 的 measure 过程
小总结
- View.measure() 是 final 修饰的,所以无论是 View,ViewGroup 都不无法重写该方法,但 measure() 会调用 onMeasure(),所以可通过重写 onMeasure(),完成自定义控件的测量
- 直接继承 View 的自定义控件需要重写 onMeasure() 并计算 WRAP_CONTENT 时的高宽,否则 WRAP_CONTENT 和 MATCH_PARENT 效果相同
- ViewGroup 自身是没有重写 onMeasure() 的,原因是不同的 ViewGroup 子类是有不同的布局特性。所以直接继承 ViewGroup 的自定义控件需要重写 onMeasure()
1. 相关 API
获取测量结果:
- int getMeasuredWidth()
- int getMeasuredHeight()
测量相关方法:
-
void setMeasuredDimension(int, int)
-
int getPaddingLeft()
-
int getPaddingTop()
-
int getPaddingRight()
-
int getPaddingBottom()
-
int getPaddingStart()
-
int getPaddingEnd()
-
setPadding(int left, int top, int right, int bottom)
-
ViewParent getParent()
2. 从 DecorView 到 View 的 measure 原理分析
/** * ViewRootImpl 类 */
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
/** * View 类 */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
/** * ViewGroup 类,见标题【MeasureSpec 和 LayoutParams 关系】 */
protected void measureChildWithMargins(View, int, int, int, int) {
...
}
- measure() 是 final 修饰的,所以无论是 View,ViewGroup 都无法重写该方法
3. ViewGroup 的 measure 原理分析
- ViewGroup 自身是没有重写 onMeasure() 的,原因是不同的 ViewGroup 子类是有不同的布局特性
/** * ViewGroup 类 */
/** * @param widthMeasureSpec ViewGroup 宽度 * @param heightMeasureSpec ViewGroup 高度 */
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
/** * @param child 子 View 的 MeasureSpec * @param parentWidthMeasureSpec ViewGroup 宽度 * @param parentHeightMeasureSpec ViewGroup 高度 */
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(
parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight,
lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(
parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom,
lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/** * 见标题【2.4. MeasureSpec 计算】 */
protected void measureChildWithMargins(View, int, int, int, int) {
...
}
4. View 的 measure 原理分析
/** * View 类 */
/** * @param widthMeasureSpec 宽度 MeasureSpec * @param heightMeasureSpec 高度 MeasureSpec */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/** * @param size 默认大小 * @param measureSpec 父容器约束 * @return View 测量后的 MeasureSpec */
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;
}
/** * mBackground: Drawable 对象 * mMinWidth: 对应 android.minWidth 属性 * getMinimumWidth(): Drawable 对象的原始宽度 */
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
/** * Drawable 类 */
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
5. 在 Activity 生命周期中获取测量结果解决方案
由于 View 的 measure 过程与 Activity 生命周期不同步,所以在 Activity 生命周期中直接获取测量结果会有问题,解决方案有以下几种: 方法一:
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
方法二:通过 post() 将消息投递到消息队列尾部,当 Looper 调用该消息时,View 已经初始化好了
@Override
protected void onStart() {
super.onStart();
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
方法三:
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
@Override
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
view.getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
方法四:不推荐,原因是 View 为 MATCH_PARENT 时,不可用
@Override
protected void onStart() {
super.onStart();
// View 为 WRAP_CONTENT 时
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST); // 取最大值
int heghtMeasureSpec = widthMeasureSpec;
view.measure(widthMeasureSpec, getMinimumHeight());
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
// View 为 dp / px 时,假设高宽依次是100px,200px
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heghtMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, getMinimumHeight());
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
四、View 的 layout 过程
小总结
- 与 measure 过程原因类似,直接继承 ViewGroup 的自定义控件需要重写 onLayout()
1. 相关 API
获取布局结果:
- getTop()
- getBottom()
- getLeft()
- getRight()
- getWidth()
- getHeight()
2. 从 DecorView 到 View 的 layout 原理分析
/** * ViewRootImpl 类 */
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
...
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false);
if (validLayoutRequesters != null) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
/** * ViewGroup 类 */
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
/** * View 类 */
/** * 见下述【普通View的layout原理】 */
public void layout(int l, int t, int r, int b) {
...
}
/** * FrameLayout 类 */
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
3. View 的 layout 原理分析
- 无论是 View 还是 ViewGroup 的 onLayout() 都是未定义,所以直接继承 ViewGroup 的自定义控件需要重写 onLayout()
/** * View 类 */
/** * @param Left/Top:与父容器的相对偏移量 * @param Right/Bottom: measure() 后与父容器的相对偏移量 */
public void layout(int l, int t, int r, int b) {
...
// 确定当前 View 的大小与相对父容器的位置
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 调用 onLayout(...)
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
/** * true: 当前 View(注意此处为父容器)是 ViewGroup,且模式为 LAYOUT_MODE_OPTICAL_BOUNDS * ViewGroup 模式有两种: * LAYOUT_MODE_CLIP_BOUNDS:默认模式,表示边界未加工的 * LAYOUT_MODE_OPTICAL_BOUNDS:大体含义是支持特效,如阴影、暖色、冷色 */
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
/** * 设置 ViewGroup 为 LAYOUT_MODE_OPTICAL_BOUNDS 时,当前 ViewGroup 的位置 */
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
...
return setFrame(...);
}
/** * Assign a size and position to this view. * 指定当前 View 的大小与相对父容器的位置 */
protected boolean setFrame(int left, int top, int right, int bottom) {
...
}
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
五、View 的 draw 过程
小总结
- 有显示内容的自定义控件需要重写 onDraw(Canvas),以便绘制自身的显示内容
1. 相关 API
无
2. 从 DecorView 到 View 的 draw 原理分析
- 由于源代码过于复杂,并且设计到硬件渲染,故上述流程仅供参考,下述贴出部分源代码,详细过程见 Android 源代码
/** * ViewRootImpl 类 */
private void performDraw() {
if (!mAttachInfo.mScreenOn && !mReportNextDraw) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}
private void draw(boolean fullRedrawNeeded) {
...
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
scrollToRectOrFocus(null, false);
...
final Rect dirty = mDirty;
...
if (!dirty.isEmpty() || mIsAnimating) {
if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
// Draw with hardware renderer.
...
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
...
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
Canvas canvas;
...
try {
...
try {
...
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
...
}
return true;
}
/** * DecorView 类 */
private Drawable mMenuBackground;
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
/** * ViewGroup 类 */
@Override
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
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);
}
/** * View 类 */
/** * 见下述【普通View的draw原理】 */
public void draw(Canvas canvas) {
...
}
3. View 的 draw 原理分析
- 绘制背景:Android 19 采用上述源码中的代码块实现。Android 21 则通过 drawBackground(Canvas) 实现
/**
* 绘制步骤:
* 1. 绘制背景
* 2. 如果需要,在做淡入淡出处理前保存画布图层???(???:不确定,下同)
* 3. 绘制 View 内容
* 4. 绘制子 View 的内容
* 5. 如果需要,绘制淡入淡出边界和恢复图层???
* 6. 绘制附加内容,如滚轮等
*
* 官方释义:
* 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)
*/
public void draw(Canvas canvas) {
// 调整当前画布的边界???
if (mClipBounds != null) {
canvas.clipRect(mClipBounds);
}
// dirtyOpaque:true,透明
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;
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
final Drawable background = mBackground;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
// 透明布局处理,貌似做了一些优化???
...
}
4. ViewGroup 的 draw 原理分析
- 由于该部分涉及的源代码复杂程度高,故上述流程仅供参考,下述贴出部分源代码,详细过程见 Android 源代码
- 由于该部分涉及的源代码复杂程度高,所以基本只体现关键方法,其他则简要说明,请对照 Android 源码查看
/** * ViewGroup */
@Override
protected void dispatchDraw(Canvas canvas) {
final int count = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
// 动画执行前准备:创建所有子 View 的绘制内容缓存、触发动画开始事件
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
...
for (int i = 0; i < count; i++) {
final View child = children[i];
...
child.buildDrawingCache(true);
}
...
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
// 调整 padding:现场保护
int saveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
saveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
// 绘制子 View 内容,并执行动画效果
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
/* 是否按指定顺序绘制子 View 的内容 */
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
} else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
// 绘制隐藏子 View 的内容
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
...
// 调整 padding:现场还原
if (clipToPadding) {
canvas.restoreToCount(saveCount);
}
// 状态标识等更新,并触发动画结束事件
// mGroupFlags might have been updated by drawChild()
flags = mGroupFlags;
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
mLayoutAnimationController.isDone() && !more) {
// We want to erase the drawing cache and notify the listener after the
// next frame is drawn because one extra invalidate() is caused by
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
public void run() {
notifyAnimationListener();
}
};
post(end);
}
}
/** * 绘制 View 内容,并执行动画效果 */
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
/** * View 类 */
/** * 该方法只能通过 ViewGroup.drawChild() 调用 */
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
// 动画执行
final Animation a = getAnimation();
if (a != null) {
more = drawAnimation(parent, drawingTime, a, scalingRequired);
...
} else {
...
}
...
// 显示内容准备
DisplayList displayList = null;
Bitmap cache = null;
boolean hasDisplayList = false;
if (caching) {
...
hasDisplayList = canHaveDisplayList();
...
cache = getDrawingCache(true);
...
}
...
displayList = getDisplayList();
...
final boolean hasNoCache = cache == null || hasDisplayList;
final boolean offsetForScroll = cache == null && !hasDisplayList &&
layerType != LAYER_TYPE_HARDWARE;
// 画布调整
...
restoreTo = canvas.save();
...
canvas.translate(mLeft - sx, mTop - sy);
...
canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop);
...
// 绘制 View 内容
if (hasNoCache) {
boolean layerRendered = false;
...
if (!layerRendered) {
if (!hasDisplayList) {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
} else {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags);
}
}
} else if (cache != null) {
...
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
}
// 画布调整:现场还原
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
// 状态标识等更新,结束动画效果
if (a != null && !more) {
if (!hardwareAccelerated && !a.getFillAfter()) {
onSetAlpha(255);
}
parent.finishAnimatingView(this, a);
}
if (more && hardwareAccelerated) {
if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
// alpha animations should cause the child to recreate its display list
invalidate(true);
}
}
mRecreateDisplayList = false;
return more;
}
参考
- 《Android 开发艺术探索》(任玉刚 著) 第四章 View 的工作原理
- Android 19 源代码(主要)
- Android 21 源代码
修订时间
- 2017-11-28:初稿
- 2017-11-29:追加目录
- 2018-12-06:发现流程图有些不好看懂,尴尬,更换了流程图,另外补充了些内容,主要是追加 FrameLayout 的源代码
声明
限于作者水平有限,出错难免,请积极拍砖! 欢迎任何形式的转载,转载请保留本文原文链接:juejin.cn/post/684490…
结言
花了 N 久的时间把这篇博客给写完了。以后复习或继续深入理解 View 工作原理也轻松了许多。
该篇博客主要以流程以及源代码注释来说明 View 工作原理。论文字描述的详细程度要比任大大的《Android 开发艺术探索》要简略许多,比较不容易阅读。所以还是推荐大家阅读任大大的《Android 开发艺术探索》。
另外,CSDN 居然无故把我的账户给封了,无语啊。没奈何,只好把博客搬到稀土掘金上了
今天的文章Android View 的工作原理分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/18338.html