androidview绘制过程,深⼊理解Android中View绘制的三⼤流程前⾔
最近对Android中View的绘制机制有了⼀些新的认识,所以想记录下来并分享给⼤家。View的⼯作流程主要是指measure、layout、draw 这三⼤流程,即测量、布局和绘制,其中measure确定View的测量宽⾼,layout根据测量的宽⾼确定View在其⽗View中的四个顶点的位置,⽽draw则将View绘制到屏幕上,这样通过ViewGroup的递归遍历,⼀个View树就展现在屏幕上了。
说的简单,下⾯带⼤家⼀步⼀步从源码中分析:
Android的View是树形结构的:
基本概念
在介绍View的三⼤流程之前,我们必须先介绍⼀些基本的概念,才能更好地理解这整个过程。
Window的概念
Window表⽰的是⼀个窗⼝的概念,它是站在WindowManagerService⾓度上的⼀个抽象的概念,Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,只要有View的地⽅就⼀定有Window。
这⾥需要注意的是,这个抽象的Window概念和PhoneWindow这个类并不是同⼀个东西,PhoneWindow表⽰的是⼿机屏幕的抽象,它充当Activity和DecorView之间的媒介,就算没有PhoneWindow也是可以展⽰View的。
抛开⼀切,仅站在WindowManagerService的⾓度上,Android的界⾯就是由⼀个个Window层叠展现的,⽽Window⼜是⼀个抽象的概念,它并不是实际存在的,它是以View的形式存在,这个View就是DecorView。
关于Window这⽅⾯的内容,我们这⾥先了解⼀个⼤概
DecorView的概念
DecorView是整个Window界⾯的最顶层View,View的测量、布局、绘制、事件分发都是由DecorView往下遍历这个View树。DecorView作为顶级View,⼀般情况下它内部会包含⼀个竖直⽅向的LinearLay
out,在这个LinearLayout⾥⾯有上下两个部分(具体情况和Android的版本及主题有关),上⾯是【标题栏】,下⾯是【内容栏】。在Activity中我们通过setContentView所设置的布局⽂件其实就是被加载到【内容栏】中的,⽽内容栏的id是content,因此指定布局的⽅法叫setContent().
ViewRoot的概念
ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三⼤流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完之后,会讲DecorView添加到Window中,同时会创建对应的ViewRootImpl,并将ViewRootImpl和DecorView建⽴关联,并保存到WindowManagerGlobal对象中。
WindowManagerGlobal.java
root = new Context(), display);
root.setView(view, wparams, panelParentView);
View的绘制流程是从ViewRoot的performTraversals⽅法开始的,它经过measure、layout和draw三个过程才能最终将⼀个View绘制出来,⼤致流程如下图:
Measure测量
为了更好地理解View的测量过程,我们还需要理解MeasureSpec,它是View的⼀个内部类,它表⽰对View的测量规格。MeasureSpec 代表⼀个32位int值,⾼2位代表SpecMode(测量模式),低30位代表SpecSize(测量⼤⼩),我们可以看看它的具体实现:
MeasureSpec.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* UNSPECIFIED 模式:
* ⽗View不对⼦View有任何限制,⼦View需要多⼤就多⼤
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* EXACTYLY 模式:
* ⽗View已经测量出⼦Viwe所需要的精确⼤⼩,这时候View的最终⼤⼩
* 就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* AT_MOST 模式:
* ⼦View的最终⼤⼩是⽗View指定的SpecSize值,并且⼦View的⼤⼩不能⼤于这个值,* 即对应wrap_content这种模式
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
//将size和mode打包成⼀个32位的int型数值
//⾼2位表⽰SpecMode,测量模式,低30位表⽰SpecSize,某种测量模式下的规格⼤⼩public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//将32位的MeasureSpec解包,返回SpecMode,测量模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格⼤⼩
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
//...
}
MeasureSpec通过将SpecMode和SpecSize打包成⼀个int值来避免过多的对象内存分配,并提供了打包和解包的⽅法。
SpecMode有三种类型,每⼀类都表⽰特殊的含义:
UNSPECIFIED
⽗容器不对View有任何限制,要多⼤就给多⼤,这种情况⼀般⽤于系统内部,表⽰⼀种测量的状态;
EXACTLY
⽗容器已经检测出View所需的精确⼤⼩,这个时候View的最终打消就是SpecSize所指定的值。它对应于LayoutParams中的
match_parent和具体数值这两种模式。
AT_MOST
⽗容器指定了⼀个可⽤⼤⼩即SpecSize,View的⼤⼩不能⼤于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams 中wrap_content。
View的MeasureSpec是由⽗容器的MeasureSpec和⾃⼰的LayoutParams决定的,但是对于DecorView来说有点不同,因为它没有⽗类。在ViewRootImpl中的measureHierarchy⽅法中有如下⼀段代码展⽰了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desireWindowHeight是屏幕的尺⼨⼤⼩:
ViewGroup的measure
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
再看看getRootMeasureSpec⽅法:
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;
}
通过以上代码,DecorView的MeasureSpec的产⽣过程就很明确了,因为DecorView是FrameLyaout的⼦类,属于ViewGroup,对于ViewGroup来说,除了完成⾃⼰的measure过程外,还会遍历去调⽤所有⼦元素的measure⽅法,各个⼦元素再递归去执⾏这个过程。和View不同的是,ViewGroup是⼀个抽象类,他没有重写View的onMeasure⽅法,这⾥很好理解,因为每个具体的ViewGroup实现类的功能是不同的,如何测量应该让它⾃⼰决定,⽐如LinearLayout和RelativeLayout。
因此在具体的ViewGroup中需要遍历去测量⼦View,这⾥我们看看ViewGroup中提供的测量⼦View的measureChildWithMargins⽅法:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) LayoutParams();
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);
}
上述⽅法会对⼦元素进⾏measure,在调⽤⼦元素的measure⽅法之前会先通过getChildMeasureSpec⽅法来得到⼦元素的MeasureSpec。从代码上看,⼦元素的MeasureSpec的创建与⽗容器的MeasureSpec和本⾝的LayoutParams有关,此外和View的margin和⽗类的padding有关,现在看看getChildMeasureSpec的具体实现:
ViewGroup.java
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = Mode(spec);
制作android软件流程int specSize = Size(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论