Android view的绘制过程
一.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();
......
//跟踪代码后发现,wm值为上述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