简介
我们知道,在 Android 中,View 绘制告急包罗 3 大流程:
measure(丈量):告急用于确定 View 的丈量宽/高。
layout(布局):告急用于确定 View 在父容器中的放置位置。
draw(绘制):团结前面两步结果,将 View 真正绘制到屏幕上。
Android 中,告急有两种视图:View和ViewGroup,此中:
View:就是一个独立的视图
ViewGroup:一个容器组件,该容器可容纳多个子视图,即ViewGroup可容纳多个View或ViewGroup,且支持嵌套。
固然ViewGroup继续于View,但是在 View 绘制三大流程中,某些流程须要区分View和ViewGroup,它们之间的利用并不完全雷同,比如:
View和ViewGroup都须要举行 measure,确定各自的丈量宽/高。View只需直接丈量自身即可,而ViewGroup通常都必须先丈量全部子View,末了才华丈量自己
通常ViewGroup先定位自己的位置(layout),然后再定位其子View 位置(onLayout)
View须要举行 draw 过程,而ViewGroup通常不须要(固然也可以举行绘制),由于ViewGroup更多作为容器存在,起存储放置功能
measure 流程
对 View 举行丈量,告急包罗两个步骤:
求取 View 的丈量规格MeasureSpec。
依据上一步求得的MeasureSpec,对 View 举行丈量,求取得到 View 的终极丈量宽/高。
MeasureSpec
对于第一个步骤,即求取 View 的MeasureSpec,首先我们来看下MeasureSpec的源码界说:
// frameworks/base/core/java/android/view/View.javapublic static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ 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); } ...}MeasureSpec是View的一个公有静态内部类,它是一个 32 位的int值,高 2 位表现 SpecMode(丈量模式),低 30 位表现 SpecSize(丈量尺寸/丈量巨细)。
MeasureSpec将两个数据打包到一个int值上,可以镌汰对象内存分配,而且其提供了相应的工具方法可以很方便地让我们从一个int值中抽取出 View 的 SpecMode 和 SpecSize。
一个MeasureSpec表达的是:该 View 在该种丈量模式(SpecMode)下对应的丈量尺寸(SpecSize)。此中,SpecMode 有三种范例:
UNSPECIFIED:表现父容器对子View 未施加任何限定,子View 尺寸想多大就多大。
EXACTLY:如果子View 的模式为EXACTLY,则表现子View 已设置了确切的丈量尺寸,大概父容器已检测出子View 所须要简直切巨细。
这种模式对应于LayoutParams.MATCH_PARENT和子View 设置详细数值两种环境。
AT_MOST:表现自顺应内容,在该种模式下,View 的最大尺寸不能凌驾父容器的 SpecSize,因此也称这种模式为 最大值模式。
这种模式对应于LayoutParams.WRAP_CONTENT。
LayoutParams 对 View 举行丈量,最关键的一步就是计算得到 View 的MeasureSpec,子View 在创建时,可以指定差异的LayoutParams(布局参数),LayoutParams的源码告急内容如下所示:
// frameworks/base/core/java/android/view/ViewGroup.javapublic static class LayoutParams { ... /** * Special value for the height or width requested by a View. * MATCH_PARENT means that the view wants to be as big as its parent, * minus the parent's padding, if any. Introduced in API Level 8. */ public static final int MATCH_PARENT = -1; /** * Special value for the height or width requested by a View. * WRAP_CONTENT means that the view wants to be just large enough to fit * its own internal content, taking its own padding into account. */ public static final int WRAP_CONTENT = -2; /** * Information about how wide the view wants to be. Can be one of the * constants FILL_PARENT (replaced by MATCH_PARENT * in API Level 8) or WRAP_CONTENT, or an exact size. */ public int width; /** * Information about how tall the view wants to be. Can be one of the * constants FILL_PARENT (replaced by MATCH_PARENT * in API Level 8) or WRAP_CONTENT, or an exact size. */ public int height; ...}此中:
在Activity启动过程中,会调用到ActivityThread.handleResumeActivity(...),该方法就是 View 视图绘制的起始之处:
// frameworks/base/core/java/android/app/ActivityThread.javafinal void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { ... ActivityClientRecord r = performResumeActivity(token, clearHide); ... // 此处的 window 为与 Activity 绑定的 PhoneWindow,即 Activity.mWindow r.window = r.activity.getWindow(); // PhoneWindow 绑定的顶层视图:DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); // 获取与 Activity 绑定的 WindowManager,实际上是 PhoneWindow 的 WindowManager ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); ... // 添加 DecorView 到 PhoneWindow 上(相称于设置 Activity 根视图) wm.addView(decor, l); ...}此中,r.window.getDecorView()实际调用的是PhoneWindow.getDecorView(),其会返回顶层DecorView(不存在时会主动实例化):
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.javapublic class PhoneWindow extends Window implements MenuBuilder.Callback { // This is the top-level view of the window, containing the window decor. private DecorView mDecor; ... @Override public final View getDecorView() { if (mDecor == null) { installDecor(); } return mDecor; } private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); ... } ... } protected DecorView generateDecor() { // 实例化 DecorView return new DecorView(getContext(), -1); } ...}然后,r.window.getAttributes()实际调用的是Window.getAttributes():
// frameworks/base/core/java/android/view/Window.javapublic abstract class Window { private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); ... public final WindowManager.LayoutParams getAttributes() { return mWindowAttributes; }}// frameworks/base/core/java/android/view/WindowManager.javapublic interface WindowManager extends ViewManager { ... public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { public LayoutParams() { // DecorView 的布局参数为 MATCH_PARENT super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); ... } }}这里可以看到,此处r.window.getAttributes()返回的是一个WindowManager.LayoutParams实例,对应的终极宽/高布局参数为LayoutParams.MATCH_PARENT,末了通过wm.addView(decor,l)将DecorView添加到WindowManager上(终极实在是设置到ViewRootImpl上),以是DecorView的布局参数为MATCH_PARENT。
View 的绘制流程真正开始的地方为ViewRootImpl.performTraversals(),在此中,有如下代码片断:
// frameworks/base/core/java/android/view/ViewRootImpl.javaprivate void performTraversals() { ... int desiredWindowWidth; int desiredWindowHeight; ... // Ask host how big it wants to be windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); ...}private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { ... childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ...}此处的desiredWindowWidth和desiredWindowHeight是屏幕的尺寸,内部终极会调用到ViewRootImpl.getRootMeasureSpec(...),其源码如下所示:
// frameworks/base/core/java/android/view/ViewRootImpl.javaprivate 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;}ViewRootImpl.getRootMeasureSpec(...)见名知意,实在就是用来获取顶层View(即DecorView)的MeasureSpec,其逻辑如下:
View 的布局过程由View#layout(...)负责,其源码如下:
// android/view/View.java/** * @param l Left position, relative to parent * @param t Top position, relative to parent * @param r Right position, relative to parent * @param b Bottom position, relative to parent */@SuppressWarnings({"unchecked"})public void layout(int l, int t, int r, int b) { ... setFrame(l, t, r, b); ... onLayout(changed, l, t, r, b); ...}View#layout(...)告急就做了两件事:
setFrame(...):首先通过View#setFrame(...)来确定自己的布局位置,其源码如下:
// android/view/View.javaprotected boolean setFrame(int left, int top, int right, int bottom) { ... // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom;}setFrame(...)实在就是更新记载 View 的四个极点位置,这样 View 在父容器中的坐标位置就确定了。
onLayout(...):setFrame(...)是用于确定 View 自身的布局位置,而onLayout(...)告急用于确定 子View 的布局位置:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}由于 View 不包罗子组件,因此其onLayout是一个空实现。
ViewGroup layout
ViewGroup 的布局流程由ViewGroup#layout(...)负责,其源码如下:
// android/view/ViewGroup.java@UiThreadpublic abstract class ViewGroup extends View implements ViewParent, ViewManager { ... @Override public final void layout(int l, int t, int r, int b) { ... super.layout(l, t, r, b); ... }可以看到,ViewGroup#layout(...)终极也是通过View#layout(...)完成自身的布局过程,一个留意的点是,ViewGroup#layout(...)是一个final方法,因此子类无法覆写该方法,告急是ViewGroup#layout(...)方法内部对子视图动画结果举行了相干设置。
由于ViewGroup#layout(...)内部终极调用的还是View#layout(...),因此,ViewGroup#onLayout(...)就会得到回调,用于处置处罚 子View 的布局放置,其源码如下:
// android/view/ViewGroup.java@Overrideprotected abstract void onLayout(boolean changed, int l, int t, int r, int b);由于差异的ViewGroup,其布局特性差异,因此ViewGroup#onLayout(...)是一个抽象方法,交由ViewGroup子类依据自己的布局特性,摆放其 子View 的位置。
draw 流程
当 View 的丈量巨细,布局位置都确定后,就可以终极将该 View 绘制到屏幕上了。
View 的绘制过程由View#draw(...)方法负责,其源码如下:
// android/view/View.javapublic void draw(Canvas canvas) { ... /* * 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 drawBackground(canvas); // skip step 2 & 5 if possible (common case) ... // Step 2, save the canvas' layers if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } if (drawBottom) { canvas.saveLayer(left, bottom - length, right, bottom, null, flags); } if (drawLeft) { canvas.saveLayer(left, top, left + length, bottom, null, flags); } if (drawRight) { canvas.saveLayer(right - length, top, right, bottom, null, flags); } ... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ... if (drawTop) { ... canvas.drawRect(left, top, right, top + length, p); } if (drawBottom) { ... canvas.drawRect(left, bottom - length, right, bottom, p); } if (drawLeft) { ... canvas.drawRect(left, top, left + length, bottom, p); } if (drawRight) { ... canvas.drawRect(right - length, top, right, bottom, p); } ... // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas);}实在表明已经写的很清楚了,View#draw(...)告急做了以下 6 件事: