Android UI绘制流程
2017-12-14 09:36
555 查看
本文从源码(基于MTK Android 7.0)角度分析 Android 中 View 的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把 View 绘制的整个流程说明清楚,从而帮助大家对特定实现细节的相应源码进行研读。
在进行实际的分析之前,先来看下面这张图:
1.1 View 加载的整体流程
View 的加载从 Activity 的 setContentView() 方法开始,通过 PhoneWindow 实例化一个 DecorView,将 setContentView()
设置的视图填充到 DecorView 中 id 是 android.R.id.content 的 FarmeLayout 里面显示,再把 DecorView 添加到 WindowManager 管理,最后由根视图 ViewRoot 设置 View 显示在手机上。
1.2
理解 Window
Window
是 PhoneWindow 的父类,其分为三种类型:系统 Window、应用程序 Window 和子 Window。每个 Window
都会指定一个 type 值,这是一个比较重要的概念,指窗口的类型。
系统
Window:比如在手机电量低的时候,会有一个提示电量低的 Window;输入文字的时候,会弹出输入法 Window;还有搜索条
Window;来电显示 Window;Toast 对应的 Window。可以总结出来,系统 Window 是独立与我们的应用程序的,对于应用程序而言,我们理论上是无法创建系统
Window,因为没有权限,这个权限只有系统进程有。所对应的层级区间是 2000 以上。
应用程序 Window:比如 Activity 就是一个应用程序 Window,源码中给到的 type 值是 TYPE_BASE_APPLICATION
。所对应的层级区间是 1 - 99。
子 Window:所谓的子Window,是说这个Window必须要有一个父窗体,比如 PopWindow ,Dialog
等等 。所对应的层级区间是 1000 - 1999。
那么每个层级具体有那些,请看 WindowManager.LayoutParams 中的 type 值。
1.3
理解 PhoneWindow
PhoneWindow
是 Android 系统中最基本的窗口系统,每个 Activity 会创建一个。PhoneWindow 是 Activity 和 View 系统交互接口。DecorView 本质上是一个 FrameLayout,是 Activity 中所有 View 的父类。
1.4
setContentView 过程
Activity
的 setContentView() 方法本质上调用的是 PhoneWindow 的 setContentView()方法,其有 3 个重载方法,核心代码如下:
1.5 installDecor
过程
方法中,初始化 activity 的主题后,接着会根据 features 值获取不同的 layoutResource。并将 layoutResource 加载到 DecorView 里面,其中
id 是 android.R.id.content 的 ViewGroup 则赋值给 ContentView 。所以开发者设置 requestFeature() 的时候要在 setContentView() 之前调用。系统有很多种 layoutResource,这里只看一看R.layout.screen_simple
布局。核心代码如下:
的布局文件里面分为两个部分,ViewStub 和 FrameLayout。而开发者写的 layout 都会填充到 id 为 android.R.id.content
的 FrameLayout 里面。
1.6 inflate 过程
如果传入参数是 layoutResID,则需要先解析成 View。这时会调用 LayoutInflater 的 inflate() 方法。核心代码如下:
1.7 WindowManager
过程
WindowManager
是一个接口,其子类 WindowManagerImpl 通过 addView() 方法对 DecorView 添加管理,然后通过 ViewRootImpl 的 setView() 方法设置 View 显示。核心代码如下:
2.1 View 绘制的整体流程
View 的绘制会从根视图 ViewRoot 的 performTraversals() 方法开始,从上到下遍历整个视图树,每个
View 控件负责绘制自己,而 ViewGroup 还需要负责通知自己的子 View 进行绘制操作。视图绘制的过程可以分为三个步骤,分别是测量(Measure)、布局(Layout)和绘制(Draw)。
performTraversals()
的核心代码如下:
2.2
理解 MeasureSpec
为了更好地理解 View 的测量过程,我们还需要了解 MeasureSpec。MeasureSpec 表示的是一个 32 位的整型值,它的高 2 位表示测量模式 SpecMode,低 30 位表示某种测量模式下的规格大小
SpecSize。MeasureSpec 是 View 类的内部静态类,用来说明应该如何测量这个 View,其核心代码如下:
以上,我们需要重点关注代码中的以下三种模式,这个在 Measure 阶段用到。
·UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到。例如:ListView
·EXACTLY:精确测量模式,当该视图的 layout_width 或者 layout_height 指定为具体的数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。
·AT_MOST:最大值模式,当该视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
对 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同决定;对于普通的 View,它的 MeasureSpec 由父视图的 MeasureSpec 和自身的 LayoutParams 共同决定。
2.3
Measure 过程
Measure
操作用来计算 View 的实际大小,由前面的分析可以知道,视图的测量流程是从 performMeasure() 方法开始的,核心代码如下:
View,并逐个调用子 View 的 measure() 方法实现测量操作。
// 遍历测量 ViewGroup 中所有的 View
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];
// 当 View 可见性处于 GONE 状态时,不对其进行测量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 根据父容器的 MeasureSpec 和子 View 的 LayoutParams 等信息计算子 View 的 MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}下面来看看
View(ViewGroup)的 measure() 方法,最终测量时通过回调 onMeasure() 方法实现的,这个通常由 View 的特定子类自己实现,开发者也可以通过重写这个方法实现自定义 View。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
// 如果需要自定义测量过程,则子类可以重写这个方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasuredDimension 方法用于设置 View 的测量宽高
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 如果有 View 没有重写 onMeasure() 方法,则默认会直接调用 getDefaultSize() 来获得 View 的宽高
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;
}
2.4 Layout 过程
Layout 过程用来确定 View 在父容器中的布局位置,它由父容器获取子 View 的位置参数后,调用子 View 的 layout() 方法并将位置参数传入实现的,ViewRootImpl 的 performLayout() 代码如下:
方法中 onLayout() 又会被调用。在 ViewGroup 和 View 里面均没有实现 onLayout() 方法,该方法由其子类重写,这个方法的用途是父容器确定子元素的位置。
2.5 Draw 过程
Draw 操作用来将控件绘制出来,绘制的流程从 performDraw() 开始,核心代码如下:
public void draw(Canvas canvas) {
...
// 步骤一:绘制 View 的背景
drawBackground(canvas);
...
// 步骤二:如果需要的话,保存 canvas 的图层,为 fading 做准备
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步骤三:绘制 View 的内容
onDraw(canvas);
...
// 步骤四:绘制 View 的子 View
dispatchDraw(canvas);
...
// 步骤五:如果需要的话,绘制 View 的 fading 边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
...
// 步骤六:绘制 View 的装饰(例如:滚动条)
onDrawScrollBars(canvas);
}最后看一张整个 draw() 的递归流程图
在进行实际的分析之前,先来看下面这张图:
1.1 View 加载的整体流程
View 的加载从 Activity 的 setContentView() 方法开始,通过 PhoneWindow 实例化一个 DecorView,将 setContentView()
设置的视图填充到 DecorView 中 id 是 android.R.id.content 的 FarmeLayout 里面显示,再把 DecorView 添加到 WindowManager 管理,最后由根视图 ViewRoot 设置 View 显示在手机上。
1.2
理解 Window
Window
是 PhoneWindow 的父类,其分为三种类型:系统 Window、应用程序 Window 和子 Window。每个 Window
都会指定一个 type 值,这是一个比较重要的概念,指窗口的类型。
系统
Window:比如在手机电量低的时候,会有一个提示电量低的 Window;输入文字的时候,会弹出输入法 Window;还有搜索条
Window;来电显示 Window;Toast 对应的 Window。可以总结出来,系统 Window 是独立与我们的应用程序的,对于应用程序而言,我们理论上是无法创建系统
Window,因为没有权限,这个权限只有系统进程有。所对应的层级区间是 2000 以上。
应用程序 Window:比如 Activity 就是一个应用程序 Window,源码中给到的 type 值是 TYPE_BASE_APPLICATION
。所对应的层级区间是 1 - 99。
子 Window:所谓的子Window,是说这个Window必须要有一个父窗体,比如 PopWindow ,Dialog
等等 。所对应的层级区间是 1000 - 1999。
那么每个层级具体有那些,请看 WindowManager.LayoutParams 中的 type 值。
1.3
理解 PhoneWindow
PhoneWindow
是 Android 系统中最基本的窗口系统,每个 Activity 会创建一个。PhoneWindow 是 Activity 和 View 系统交互接口。DecorView 本质上是一个 FrameLayout,是 Activity 中所有 View 的父类。
1.4
setContentView 过程
Activity
的 setContentView() 方法本质上调用的是 PhoneWindow 的 setContentView()方法,其有 3 个重载方法,核心代码如下:
public void setContentView(int layoutResID) { installDecor(); ... mLayoutInflater.inflate(layoutResID, mContentParent); ... } public void setContentView(View view) { setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } public void setContentView(View view, ViewGroup.LayoutParams params) { installDecor(); ... mContentParent.addView(view, params); ... }如果传入参数是 layoutResID,则需要先解析成 View,再添加到 WindowManager 里面。如果参数传入 View,则直接添加到 WindowManager。
1.5 installDecor
过程
private void installDecor() { ... // 返回 DecorView 的实例,其中 DecorView 是 PhoneWindow 的内部类 mDecor = generateDecor(-1); ... // 初始化 Activity 主题,根据获取到的 style 对 Activity 窗口属性特征的设置,并返回 ContentView。 mContentParent = generateLayout(mDecor); }在 generateLayout()
方法中,初始化 activity 的主题后,接着会根据 features 值获取不同的 layoutResource。并将 layoutResource 加载到 DecorView 里面,其中
id 是 android.R.id.content 的 ViewGroup 则赋值给 ContentView 。所以开发者设置 requestFeature() 的时候要在 setContentView() 之前调用。系统有很多种 layoutResource,这里只看一看R.layout.screen_simple
布局。核心代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>从代码可以看出,DecorView
的布局文件里面分为两个部分,ViewStub 和 FrameLayout。而开发者写的 layout 都会填充到 id 为 android.R.id.content
的 FrameLayout 里面。
1.6 inflate 过程
如果传入参数是 layoutResID,则需要先解析成 View。这时会调用 LayoutInflater 的 inflate() 方法。核心代码如下:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { ... if (TAG_MERGE.equals(name)) { // 如果使用 merge 标签,必须设置其为根节点 并且设置 attachToRoot=true if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, inflaterContext, attrs, false); } .... } void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { ... final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { // 如果使用 include 作为根节点,抛出异常 if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { // 如果使用了 merge作为子节点,抛出异常 throw new InflateException("<merge /> must be the root element"); } else { final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // 解析子 View, 方法里面依然调用 rInflate rInflateChildren(parser, view, attrs, true); // 将 View 添加到 ViewGroup viewGroup.addView(view, params); } ... }
1.7 WindowManager
过程
WindowManager
是一个接口,其子类 WindowManagerImpl 通过 addView() 方法对 DecorView 添加管理,然后通过 ViewRootImpl 的 setView() 方法设置 View 显示。核心代码如下:
// WindowManagerGlobal.java public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); ... root.setView(view, wparams, panelParentView); } // ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ... // 最终会调用到 doTraversal() requestLayout(); ... } void doTraversal() { ... performTraversals(); ... }
2.1 View 绘制的整体流程
View 的绘制会从根视图 ViewRoot 的 performTraversals() 方法开始,从上到下遍历整个视图树,每个
View 控件负责绘制自己,而 ViewGroup 还需要负责通知自己的子 View 进行绘制操作。视图绘制的过程可以分为三个步骤,分别是测量(Measure)、布局(Layout)和绘制(Draw)。
performTraversals()
的核心代码如下:
private void performTraversals() { ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... // 执行测量流程 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... // 执行布局流程 performLayout(lp, mWidth, mHeight); ... // 执行绘制流程 performDraw(); }
2.2
理解 MeasureSpec
为了更好地理解 View 的测量过程,我们还需要了解 MeasureSpec。MeasureSpec 表示的是一个 32 位的整型值,它的高 2 位表示测量模式 SpecMode,低 30 位表示某种测量模式下的规格大小
SpecSize。MeasureSpec 是 View 类的内部静态类,用来说明应该如何测量这个 View,其核心代码如下:
/** * MeasureSpec 封装了父布局传递给子布局的布局要求,每个 MeasureSpec 代表了一组宽度和高度的要求 * MeasureSpec 由 size 和 mode 组成。 * MeasureSpecs 使用了二进制去减少对象的分配。 */ public static class MeasureSpec { // 进位大小为2的30次方(int的大小为32位,所以进位30位就是要使用int的最高位和倒数第二位也就是32和31位做标志位) private static final int MODE_SHIFT = 30; // 运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000(11后跟30个0) // (遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0) private static final int MODE_MASK = 0x3 << MODE_SHIFT; // UNSPECIFIED 模式:父 View 不对子 View 有任何限制,子 View 需要多大就多大 // 在源码中的处理和 EXACTLY 一样。当 View 的宽高值设置为 0 的时候或者没有设置宽高时,模式为 UNSPECIFIED // 0向左进位30,就是00 00000000000(00后跟30个0) public static final int UNSPECIFIED = 0 << MODE_SHIFT; // EXACTYLY 模式:父 View 已经测量出子 Viwe 所需要的精确大小,这时候 View 的最终大小 // 当设置 width 或 height 为 match_parent 时,模式为 EXACTLY,因为子 View 会占据剩余容器的空间,所以它大小是确定的 // 1向左进位30,就是01 00000000000(01后跟30个0) public static final int EXACTLY = 1 << MODE_SHIFT; // AT_MOST 模式:子 View 的最终大小是父 View 指定的 SpecSize 值,并且子 View 的大小不能大于这个值 // 当设置为 wrap_content 时,模式为 AT_MOST, 表示子 View 的大小最多是多少,这样子 View 会根据这个上限来设置自己的尺寸 // 2向左进位30,就是10 00000000000(10后跟30个0) public static final int AT_MOST = 2 << MODE_SHIFT; // 根据提供的 size 和 mode 得到一个详细的测量结果。measureSpec = size + mode;(注意:二进制的加法,不是10进制的加法!) // 这里设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值 // 例如:size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100 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); } } // 通过详细测量结果获得 mode。mode = measureSpec & MODE_MASK; // MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。 // 例如:10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值 @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } // 通过详细测量结果获得 size。size = measureSpec & ~MODE_MASK; // 原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } // 微调某个 MeasureSpec 的大小 static int adjust(int measureSpec, int delta) { final int mode = getMode(measureSpec); int size = getSize(measureSpec); if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return makeMeasureSpec(size, UNSPECIFIED); } size += delta; if (size < 0) { Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size + ") spec: " + toString(measureSpec) + " delta: " + delta); size = 0; } return makeMeasureSpec(size, mode); } // 重写的toString方法,打印mode和size的信息 public static String toString(int measureSpec) { int mode = getMode(measureSpec); int size = getSize(measureSpec); StringBuilder sb = new StringBuilder("MeasureSpec: "); if (mode == UNSPECIFIED) sb.append("UNSPECIFIED "); else if (mode == EXACTLY) sb.append("EXACTLY "); else if (mode == AT_MOST) sb.append("AT_MOST "); else sb.append(mode).append(" "); sb.append(size); return sb.toString(); } }
以上,我们需要重点关注代码中的以下三种模式,这个在 Measure 阶段用到。
·UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少用到。例如:ListView
·EXACTLY:精确测量模式,当该视图的 layout_width 或者 layout_height 指定为具体的数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。
·AT_MOST:最大值模式,当该视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。
对 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同决定;对于普通的 View,它的 MeasureSpec 由父视图的 MeasureSpec 和自身的 LayoutParams 共同决定。
2.3
Measure 过程
Measure
操作用来计算 View 的实际大小,由前面的分析可以知道,视图的测量流程是从 performMeasure() 方法开始的,核心代码如下:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { ... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ... }具体的测量操作是分发给 ViewGroup 的,由 ViewGroup 在它的 measureChild() 方法中传递给子 View,代码如下。ViewGroup 通过遍历自身所有的子
View,并逐个调用子 View 的 measure() 方法实现测量操作。
// 遍历测量 ViewGroup 中所有的 View
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];
// 当 View 可见性处于 GONE 状态时,不对其进行测量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 根据父容器的 MeasureSpec 和子 View 的 LayoutParams 等信息计算子 View 的 MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}下面来看看
View(ViewGroup)的 measure() 方法,最终测量时通过回调 onMeasure() 方法实现的,这个通常由 View 的特定子类自己实现,开发者也可以通过重写这个方法实现自定义 View。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
// 如果需要自定义测量过程,则子类可以重写这个方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasuredDimension 方法用于设置 View 的测量宽高
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 如果有 View 没有重写 onMeasure() 方法,则默认会直接调用 getDefaultSize() 来获得 View 的宽高
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;
}
2.4 Layout 过程
Layout 过程用来确定 View 在父容器中的布局位置,它由父容器获取子 View 的位置参数后,调用子 View 的 layout() 方法并将位置参数传入实现的,ViewRootImpl 的 performLayout() 代码如下:
// ViewRootImpl.java private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); ... } // View.java public void layout(int l, int t, int r, int b) { ... onLayout(changed, l, t, r, b); ... } // 空方法,子类如果是 ViewGroup 类型,则重写这个方法,实现 ViewGroup 中所有的 View 控件布局流程 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }当 ViewGroup 的位置被确定后,它在 onLayout() 中会遍历所有的子元素并调用其 layout() 方法,在 layout()
方法中 onLayout() 又会被调用。在 ViewGroup 和 View 里面均没有实现 onLayout() 方法,该方法由其子类重写,这个方法的用途是父容器确定子元素的位置。
2.5 Draw 过程
Draw 操作用来将控件绘制出来,绘制的流程从 performDraw() 开始,核心代码如下:
private void performDraw() { ... draw(fullRedrawNeeded); ... } private void draw(boolean fullRedrawNeeded) { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } ... } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { ... mView.draw(canvas); ... }可以看到最终调用到每个 View 的 draw() 方法绘制每个具体的 View,View 的绘制遵循六个步骤,代码如下:
public void draw(Canvas canvas) {
...
// 步骤一:绘制 View 的背景
drawBackground(canvas);
...
// 步骤二:如果需要的话,保存 canvas 的图层,为 fading 做准备
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步骤三:绘制 View 的内容
onDraw(canvas);
...
// 步骤四:绘制 View 的子 View
dispatchDraw(canvas);
...
// 步骤五:如果需要的话,绘制 View 的 fading 边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
...
// 步骤六:绘制 View 的装饰(例如:滚动条)
onDrawScrollBars(canvas);
}最后看一张整个 draw() 的递归流程图
相关文章推荐
- Android UI绘制流程(一)
- 源码分析android的UI绘制流程
- Android——UI(一):UI绘制流程
- Android-UI控件的绘制流程以及自定义控件的具体操作
- Android UI绘制流程(一)----布局的加载
- Android自定义View专题一 UI绘制流程
- Android UI绘制流程(二)
- Android-UI控件的绘制流程以及自定义控件的具体操作
- Android UI的绘制流程
- Android应用层View绘制流程与源码分析
- Android UI 绘制过程浅析(五)自定义View
- Android 4.4 Kitkat Phone工作流程浅析(二)__UI结构分析
- Android View的绘制流程
- Android学习自定义View(二)——View和ViewGroup绘制流程以及invalidate()
- Android中View绘制流程
- Android View绘制流程
- Android O: View的绘制流程(二):测量
- Android View的绘制流程
- Android中View绘制流程
- Android事件分发和View绘制流程分析(一)