盒子
盒子
Posts List
  1. 一.activity,window,decorView
  2. 二. measure 测量view大小
    1. MeasureSpec 测量数据含义说明
    2. 测量过程
      1. 计算自身的MeasureSpec
      2. 深度递归计算子view大小
      3. 计算自身实际大小
  3. 三. layout确定位置的过程
  4. 四. draw

view 测绘流程

一.activity,window,decorView

每个activity持有一个PhoneWindow实例,PhoneWindow继承自Window。

PhoneWindow持有DecorView实例。

DecorView 是实际显示在屏幕上的内容,它是一个FrameLayout,包含两部分内容android.R.id.background 和 LinearLayout

LinearLayout 包含Actionbar和android.R.id.content,也就是我们自定义的view的内容。它的布局是FrameLayout

通过上面的关系,能看出activity的绘制过程,可以看成是android.R.id.content的绘制过程

绘制流程分为三个步骤

  • measure 测量每个子view的大小
  • layout 确定每个子view的位置
  • draw 绘制每个子view的内容

二. measure 测量view大小

android设计理念中,view的大小由父view和子view的布局共同决定,measure计算的过程就是确定view的大小的过程,这个过程确定了两个值:

  • view的测量大小 getMeasureHeight()
  • view的大小 getHeight()

很多时候,它们是不一样的。

MeasureSpec 测量数据含义说明

MeasureSpec 是一个32位int,前二位表示测量出来的模式,后30位表示大小。
31 | 30 | 0-29
—|— | —
测量模式 | 宽或高表示位 | 测量大小

三种测量模式的解释:

  • exactly 大小已经确定
  • at_most 大小不确定,但是不能超过某个数值
  • unspecified 没有任何限制

它的作用用于帮助子view进行测绘,它传递了父view的布局大小要求,子view根据父view的MeasureSpec的要求和自身的布局参数(layout_width , layout_height)递归的计算自身的MeasureSpec和大小。

测量过程

每个view在测量自身的过程中,都会执行如下过程:

  • 计算自身的测量大小 MeasureSpec
  • 深度递归计算子view大小
  • 计算自身实际大小

分别来说明三个过程

计算自身的MeasureSpec

这个过程相对比较简单,也比较容易理解。它只涉及父view和子view ,不涉及子view的孩子。

计算表格如下:

image

深度递归计算子view大小

这个过程确定子view的MeasureSpec和实际大小

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
....
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
....
setMeasuredDimension(
resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT)
);
}
}

对于每个子view,都会执行measureChildWithMargins,这个方法确定每个子view的MeasureSpec,并调用子view的measure方法。

计算自身实际大小

当父view的每个子view都测量完成后,都会更新maxHeight和maxWidth。setMeasuredDimension 会设置父view的实际大小,这里面有个方法resolveSizeAndState ,它会根据父view的测量模式和测量大小,以及子view所占的总体大小,计算父view占的实际大小,只有在测量模式为at_most并且实际大小小于测量大小的情况下,实际大小才等于maxHeight或maxWidth。

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
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:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState&MEASURED_STATE_MASK);
}

总结来说:view只有在设置布局参数为wrap_content的情况下,测量模式才会变成at_most,也才能导致测量大小和实际大小数值不一致。这也跟我们的布局要求相同。其它时候view的大小一般都等于测量大小。

三. layout确定位置的过程

每个view通过measure确定了自身的大小后,再通过layout确定自身在父view中的位置。

如果是没有子view的叶子节点,layout的过程会调用setFrame,并在布局有变化的情况下调用onLayout方法。

setFrame 就是给view的l t r b (左上右下)赋值。

如果view是viewgroup,它包含其它节点,则layout的过程就是递归执行child的layout的过程。

四. draw

绘制的过程,从源码上来看,分为6个步骤,常见的是三个步骤 ,其中2,5,6并不常见。

自身的绘制都在onDraw方法中。

  • 绘制背景
  • 缓存画布的层,用于绘制边框渐变
  • 绘制自身内容调用onDraw
  • 绘制所有孩子
  • 绘制边框渐变
  • 绘制滑动条
支持一下
扫一扫,支持牛头码农