View的绘制流程
View绘制中主要流程分为measure,layout,draw三个阶段
measure:根据父view传递的MeasureSprc进行计算大小
layout:根据measure子View所得到的布局大小和布局参数,将子View放在合适的位置上
draw:把View对象绘制到屏幕上
Window,ViewRootlmpl,DecorView之间的联系
一个Activity包含一个Window,Window是抽象基类,是Activity和整个View系统交互的接口,只有一个子类实现类PhoneWindow,提供了一系列窗口的方法.比如设置背景,标题等.
一个PhoneWindow对应一个DecorView跟一个ViewRootlmpl,DecorView是ViewTree里面的顶层布局,是继承FrameLayout,包含两个子View 一个id=statusBarBackground的View和LineaLayout\
LineaLayout里面包含title和content title就是平常使用的titleBar或者ActionBar
Contenty也是继承FrameLayout activity通过setContext()加载布局的时候加载到这个View上
ViewRootlmpl是建立DecorView和Window之间的联系
这三个阶段的核心入口是在ViewRootlmpl类的preformTraversals()方法中
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
核心还是这三个步骤,就是判断根据之前的状态判断是否需要重新 measure,是否需要重新 layout ,是否需要重新 draw。
MeasureSpeac:封装了从父View 传递给到子View的布局需求。每个MeasureSpec代表宽度或高度的要求。每个MeasureSpec都包含了size(大小)和mode(模式)
MeasureSpeac:一个32位二进制的整数型,前面2位代表的是mode,后面30位代表的是size。mode 主要分为3类
EXACTLY:父容器已经测量出子View的大小。对应是 View 的LayoutParams的match_parent 或者精确数值。
AT_MOST:父容器已经限制子view的大小,View 最终大小不可超过这个值。对应是 View 的LayoutParams的wrap_content
UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。
封装了从父 View 传递给到子 View 的布局需求:View 的 MeasureSpec 并不是父 View 独自决定,它是根据父 view 的MeasureSpec加上子 View 的自己的 LayoutParams
View 测量流程是父 View 先测量子 View,等子 View 测量完了,再来测量自己。在ViewGroup 测量子 View 的入口就是 measureChildWithMargins
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//获取子View的LayoutParam
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//通过父View的MeasureSpec和子View的margin,父View的padding计算,算出子View的MeasureSpec
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);
//通过计算出来的MeasureSpec,让子View自己测量。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//计算子View的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 父View是EXACTLY的
case MeasureSpec.EXACTLY:
//子View的width或height是个精确值,则size为精确值,mode为 EXACTLY
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//子View的width或height是MATCH_PARENT,则size为父视图大小,mode为 EXACTLY
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
//子View的width或height是WRAP_CONTENT,则size为父视图大小,mode为 AT_MOST
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 2、父View是AT_MOST的
case MeasureSpec.AT_MOST:
//子View的width或height是个精确值,则size为精确值,mode为 EXACTLY
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//子View的width或height是MATCH_PARENT,则size为父视图大小,mode为 AT_MOST
} 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;
resultMode = MeasureSpec.AT_MOST;
//子View的width或height是MATCH_PARENT,则size为父视图大小,mode为 AT_MOST
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父View是UNSPECIFIED的
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
当父View的mode是EXACTLY的时候:说明父View的大小是确定的
子View的宽或高是MATCH_PARENT:
子View的宽或高是WRAP_CONTENT:子View是包裹布局,说明子View的大小还不确定,所以子View最大不能超过父View的大小mode=AT_MOST。
子View的宽或高是具体数值:子viewd大小已经固定了,子View的大小就是固定这个数值,mode=EXACTLY。
当父View的mode是AT_MOST的时候:说明父View大小是不确定的。
子View的宽或高是MATCH_PARENT:父View大小是不确定的,子View是填充布局情况,也不能确定大小,所以View大小不能超过父View的大小,mode=AT_MOST
子View的宽或高是WRAP_CONTENT:子View是包裹布局,大小不能超过父View的大小,mode=AT_MOST。
子View的宽或高是具体数值:子viewd大小已经固定了,子View的大小就是固定这个数值,mode=EXACTLY。
需要注意一点就是,此时的MeasureSpec并不是View真正的大小,只有setMeasuredDimension之后才能真正确定View的大小。
measure:主要功能就是测量设置 View 的大小。该方法是 final 类型,子类不能覆盖,在方法里面会调用 onMeasure(),我们可以复写 onMeasure() 方法去测量设置 View 的大小。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
/*-----------省略代码---------------*
onMeasure(widthMeasureSpec, heightMeasureSpec);
/*-----------省略代码---------------*/
}
在 onMeasure( ) 方法中(onMeasure( ) 方法就是执行测量设置 View 代码的核心所在)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getSuggestedMinimumWidth():(这里返回的建议最小值就是我们xml 布局中用的属性 minWidth或者是背景大小)
protected int getSuggestedMinimumWidth() {
//返回建议 View 设置最小值宽度
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
getDefaultSize:主要作用就是根据View的建议最小值,结合父View传递的measureSpec,得出并返回measureSpec
(并不是父View 独自决定,它是根据父 view 的MeasureSpec加上子vIew的自己的LayoutParams,通过相应的规则转化而得到的大小)
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//获取父View传递过来的模式
int specMode = MeasureSpec.getMode(measureSpec);
//获取父View传递过来的大小
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;//View的大小父View未定,设置为建议最小值
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
setMeasuredDimension 作用就是将测量好的宽跟高进行存储。在onMeasure() 必须调用这个方法,不然就会抛出 IllegalStateException 异常
layout()
作用描述:
measure() 方法中我们已经测量出View的大小,根据这些大小,我们接下来就需要确定 View 在父 View 的位置进行排版布局,这就是layout 作用。
对 View 进行排版布局,还是要看父 View,也就是 ViewGroup。
draw()
经过前面两部的测量跟布局之后,接下来就是绘制了,也就是真正把 View 绘制在屏幕可见视图上。draw()作用就是绘制View 的背景,内容,绘制子View,还有前景跟滚动条
draw 过程中一共分成7步,其中两步我们直接直接跳过不分析了
第一步:drawBackground(canvas): 作用就是绘制 View 的背景。
第二步:onDraw(canvas) :绘制 View 的内容。View 的内容是根据自己需求自己绘制的,所以方法是一个空方法,View的继承类自己复写实现绘制内容。
第三步:dispatchDraw(canvas):遍历子View进行绘制内容。在 View 里面是一个空实现,ViewGroup 里面才会有实现。在自定义 ViewGroup 一般不用复写这个方法,因为它在里面的实现帮我们实现了子 View 的绘制过程,基本满足需求。
第四步:onDrawForeground(canvas):对前景色跟滚动条进行绘制。
第五步:drawDefaultFocusHighlight(canvas):绘制默认焦点高亮