Android绘制过程
一.VIew的绘制流程
View
的绘制流程是从ViewRootImpl
的performTraversals
方法开始,它经过measure
、layout
和draw
三个过程才能最终将一个View
绘制出来。
!!!记住:
//===========ActivityThread.java==========
final void handleResumeActivity(...) {
......
//跟踪代码后发现其初始赋值为mWindow = new PhoneWindow(this, window, activityConfigCallback);
r.window = r.activity.getWindow();
//从PhoneWindow实例中获取DecorView
View decor = r.window.getDecorView();
......
//跟踪代码后发现,vm值为上述PhoneWindow实例中获取的WindowManager。
ViewManager wm = a.getWindowManager();
......
//当前window的属性,从代码跟踪来看是PhoneWindow窗口的属性
WindowManager.LayoutParams l = r.window.getAttributes();
......
wm.addView(decor, l);
......
}
小结:
在ActivityThread中传递获得decorView以及windowManager以及layoutParams参数,decor与windowManager以组合方式组合在一起,然后调用windowManager的AddView将decor以及Params传递,实际上是间接调用ViewRootImpl类中的performTraversals(),从而实现视图的绘制。
ViewRootImpl中的performTraversals()
// =====================ViewRootImpl.java=================
/*
上述代码中就是一个完成的绘制流程,对应上了第一节中提到的三个步骤:
1)performMeasure():从根节点向下遍历View树,完成所有ViewGroup和View的测量工作,计算出所有ViewGroup和View显示出来需要的高度和宽度;
2)performLayout():从根节点向下遍历View树,完成所有ViewGroup和View的布局计算工作,根据测量出来的宽高及自身属性,计算出所有ViewGroup和View显示在屏幕上的区域;
3)performDraw():从根节点向下遍历View树,完成所有ViewGroup和View的绘制工作,根据布局过程计算出的显示区域,将所有View的当前需显示的内容画到屏幕上。
咱们后续就是通过对这三个方法来展开研究整个绘制过程
*/
private void performTraversals() {
......
Rect frame = mWinFrame;
......
mWidth = frame.width();
mHeight = frame.heigth();
......
//一个View的MeasureSpec由父布局MeasureSpec和自身的LayoutParams共同产生。父布局的MeasureSpec从何而来?从父布局的父布局而来。最顶层的布局是DecorView,DecorView的MeasureSpec是通过ViewRootImpl中的getRootMeasureSpec方法得到的
//其中mWidth和mHeight是windowManagerService服务计算的Activity窗口大小
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, mWidth, mHeight);
......
performDraw();
}
/*
1)基于window的layout params,在window中为root view 找出measure spec。(笔者注:也就是找出DecorView的MeasureSpec,这里的window也就是PhoneWindow了)
2)参数windowSize:window的可用宽度和高度值。
3)参数rootDimension:window的宽/高的layout param值。
4)返回值:返回用于测量root view的MeasureSpec。
*/
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;
}
MeasureSpec
是view的一个内部类
其中有三个模式:
SepcMode的三种类型:
-
EXACTLY: 对应match_parent
-
AT_MOST 对应wrap_parent
-
UNSPECIFIED 父容器不对View有任何限制
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 EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
//用偏移以及与操作将模式以及size组合在一起,由于有三个模式占两位,所以size最多就是30位(2^30-1)
public static int makeMeasureSpec(
ViewGroup.LayoutParams:
/*
1)LayoutParams被view用于告诉它们的父布局它们想要怎样被布局。(笔者注:字面意思就是布局参数)
2)该LayoutParams基类仅仅描述了view希望宽高有多大。对于每一个宽或者高,可以指定为以下三种值中的一个:MATCH_PARENT,WRAP_CONTENT,an exact number。(笔者注:FILL_PARENT从API8开始已经被MATCH_PARENT取代了,所以下文就只提MATCH_PARENT)
3)MATCH_PARENT:意味着该view希望和父布局尺寸一样大,如果父布局有padding,则要减去该padding值。
4)WRAP_CONTENT:意味着该view希望其大小为仅仅足够包裹住其内容即可,如果自己有padding,则要加上该padding值。
5)对ViewGroup不同的子类,也有相应的LayoutParams子类。
6)其width和height属性对应着layout_width和layout_height属性
*/
public static class LayoutParams {
public static final int MATCH_PARENT = -1;
public static final int WRAP_CONTENT = -2;
public int width;
public int height;
......
}
从ViewRootImpl的performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)开始测量
mView是decorView,在ActivityThread中的addView方法中传递进来
//ViewRootImpl
//
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
}
//ActivityThread.addView -> ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
......
mView = view;
......
mWindowAttributes.copyFrom(attrs);
......
}
View中的Measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
/*
1)该方法被调用,用于找出view应该多大。父布局在witdh和height参数中提供了限制信息;
2)一个view的实际测量工作是在被本方法所调用的onMeasure(int,int)方法中实现的。所以,只有onMeasure(int,int)可以并且必须被子类重写(笔者注:这里应该指的是,ViewGroup的子类必须重写该方法,才能绘制该容器内的子view。如果是自定义一个子控件,extends View,那么并不是必须重写该方法);
3)参数widthMeasureSpec:父布局加入的水平空间要求;
4)参数heightMeasureSpec:父布局加入的垂直空间要求。
系统将其定义为一个final方法,可见系统不希望整个测量流程框架被修改。
*/
View中的OnMeasure
//这些方法稍后会被用到
/* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//用来储存View测出的宽和高
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;
}
// 返回建议该view应该使用的最小宽度值。该方法返回了view的最小宽度值和背景的最小宽度值(链接android.graphics.drawable.Drawable#getMinimumWidth())之间的最大值。
//当在onMeasure(int,int)使用时,调用者应该仍然确保返回的宽度值在父布局的要求之内。
//返回值:view的建议最小宽度值
// 这其中提到的"mininum width“指的是在xml布局文件中该view的“android:minWidth"属性值,“background's minimum width”值是指“android:background”的宽度。该方法的返回值就是两者之间较大的那一个值,用来作为该view的最小宽度值,现在应该很容易理解了吧,当一个view在layout文件中同时设置了这两个属性时,为了两个条件都满足,自然要选择值大一点的那个了
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
// measuredWidth:该view被测量出宽度值。
// measuredHeight:该view被测量出的高度值
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
.....
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
//View成员变量中的值被确定下来
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
......
}
//View中
public static final int MEASURED_SIZE_MASK = 0x00ffffff;
//获取原始的测量宽度值,一般会拿这个方法和layout执行后getWidth()方法做比较。该方法需要在setMeasuredDimension()方法执行后才有效,否则返回值为0。
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
开始测量:
//=============DecorView.java=============
最后流程图大概如下:
小结:
从ActivityThread开始获得DecorView与WindowManager并获得好DecorView的LayoutParams,并一层一层传递下去,ViewGroup会递归传递,而View会设置自身大小,直到ViewGroup等待自身所有View设置好之后设置自身的值
设置值的规则:
MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求!
ViewGroup中辅助重写onMeasure的几个重要方法介绍:
//================ViewGroup.java===============
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
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);
}
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
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);
}
/// spec参数 表示父View的MeasureSpec
// padding参数 父View的Padding+子View的Margin,父View的大小减去这些边距,才能精确算出
// 子View的MeasureSpec的size
// childDimension参数 表示该子View内部LayoutParams属性的值(lp.width或者lp.height)
// 可以是wrap_content、match_parent、一个精确指(an exactly size),
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //获得父View的mode
int specSize = MeasureSpec.getSize(spec); //获得父View的大小
//父View的大小-自己的Padding+子View的Margin,得到值才是子View的大小。
int size = Math.max(0, specSize - padding);
int resultSize = 0; //初始化值,最后通过这个两个值生成子View的MeasureSpec
int resultMode = 0; //初始化值,最后通过这个两个值生成子View的MeasureSpec
switch (specMode) {
// Parent has imposed an exact size on us
//1、父View是EXACTLY的 !
case MeasureSpec.EXACTLY:
//1.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//1.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST 。
}
break;
// Parent has imposed a maximum size on us
//2、父View是AT_MOST的 !
case MeasureSpec.AT_MOST:
//2.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY 。
}
//2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST
}
//2.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size; //size为父视图大小
resultMode = MeasureSpec.AT_MOST; //mode为AT_MOST
}
break;
// Parent asked to see how big we want to be
//3、父View是UNSPECIFIED的 !
case MeasureSpec.UNSPECIFIED:
//3.1、子View的width或height是个精确值 (an exactly size)
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension; //size为精确值
resultMode = MeasureSpec.EXACTLY; //mode为 EXACTLY
}
//3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0; //size为0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED
}
//3.3、子View的width或height为 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0; //size为0! ,其值未定
resultMode = MeasureSpec.UNSPECIFIED; //mode为 UNSPECIFIED
}
break;
}
//根据上面逻辑条件获取的mode和size构建MeasureSpec对象。
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
-
如果父View的MeasureSpec 是EXACTLY,说明父View的大小是确切的,(确切的意思很好理解,如果一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大)。
1、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是确切,子View的大小又MATCH_PARENT(充满整个父View),那么子View的大小肯定是确切的,而且大小值就是父View的size。所以子View的size=父View的size,mode=EXACTLY
2、如果子View 的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根据自己的content 来决定的,但是子View的毕竟是子View,大小不能超过父View的大小,但是子View的是WRAP_CONTENT,我们还不知道具体子View的大小是多少,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 调用的时候才去真正测量子View 自己content的大小(比如TextView wrap_content 的时候你要测量TextView content 的大小,也就是字符占用的大小,这个测量就是在child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的时候,才能测出字符的大小,MeasureSpec 的意思就是假设你字符100px,但是MeasureSpec 要求最大的只能50px,这时候就要截掉了)。通过上述描述,子View MeasureSpec mode的应该是AT_MOST,而size 暂定父View的 size,表示的意思就是子View的大小没有不确切的值,子View的大小最大为父View的大小,不能超过父View的大小(这就是AT_MOST 的意思),然后这个MeasureSpec 做为子View measure方法 的参数,做为子View的大小的约束或者说是要求,有了这个MeasureSpec子View再实现自己的测量。
3、如果如果子View 的layout_xxxx是确定的值(200dp),那么就更简单了,不管你父View的mode和size是什么,我都写死了就是200dp,那么控件最后展示就是就是200dp,不管我的父View有多大,也不管我自己的content 有多大,反正我就是这么大,所以这种情况MeasureSpec 的mode = EXACTLY 大小size=你在layout_xxxx 填的那个值。
-
如果父View的MeasureSpec 是AT_MOST,说明父View的大小是不确定,最大的大小是MeasureSpec 的size值,不能超过这个值。
1、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不确定(只知道最大只能多大),子View的大小MATCH_PARENT(充满整个父View),那么子View你即使充满父容器,你的大小也是不确定的,父View自己都确定不了自己的大小,你MATCH_PARENT你的大小肯定也不能确定的,所以子View的mode=AT_MOST,size=父View的size,也就是你在布局虽然写的是MATCH_PARENT,但是由于你的父容器自己的大小不确定,导致子View的大小也不确定,只知道最大就是父View的大小。
2、如果子View 的layout_xxxx是WRAP_CONTENT,父View的大小是不确定(只知道最大只能多大),子View又是WRAP_CONTENT,那么在子View的Content没算出大小之前,子View的大小最大就是父View的大小,所以子View MeasureSpec mode的就是AT_MOST,而size 暂定父View的 size。
3、如果如果子View 的layout_xxxx是确定的值(200dp),同上,写多少就是多少,改变不了的。
-
如果父View的MeasureSpec 是UNSPECIFIED(未指定),表示没有任何束缚和约束,不像AT_MOST表示最大只能多大,不也像EXACTLY表示父View确定的大小,子View可以得到任意想要的大小,不受约束
1、如果子View 的layout_xxxx是MATCH_PARENT,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求, 那么对于子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0
2、同上...
3、如果如果子View 的layout_xxxx是确定的值(200dp),同上,写多少就是多少,改变不了的(记住,只有设置的确切的值,那么无论怎么测量,大小都是不变的,都是你写的那个值)
具体:https://www.jianshu.com/p/5a71014e7b1b?from=singlemessage
Layout过程
根据测量的值以及一些其他的参数设定布局(如gravity)
ViewGroup中的layout函数
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;
}
}
//super
public final void layout(int l, int t, int r, int b) {
.....
//设置View位于父视图的坐标轴
boolean changed = setFrame(l, t, r, b);
//判断View的位置是否发生过变化,看有必要进行重新layout吗
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
//调用onLayout(changed, l, t, r, b); 函数
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
.....
}
//回调 View
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
//对于ViewGroup 来说,唯一的差别就是ViewGroup中多了关键字abstract的修饰,要求其子类必须重载onLayout函数
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
draw过程
public 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
...
background.draw(canvas);
...
// skip step 2 & 5 if possible (common case)
...
// Step 2, save the canvas' layers
...
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, 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) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, right, top + length, p);
}
...
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
}
大致流程:
啰嗦链接: