View绘制机制

View绘制机制

十一月 09, 2017

View的绘制机制

1. view树的绘制流程

(当Activity接收到用户触摸焦点的时候, 会被请求去绘制布局;请求是由安卓Framework层去处理绘制,从根节点去对布局进行测量和绘制ViewRoot类中)

measure -> layout -> draw

measure: 是否重新计算视图大小;

(递归)view 会对所有子元素进行测量, 测量过程从父的ViewGroup传递到子View里面, 经过子元素的递归, 测量好所有子元素的长度, 再进行递归, 反复之后就完成了ViewGroup的测量;

layout: 是否需要重新安置视图位置;

draw: 是否需要重绘;

2. measure

2.1 ViewGroup.LayoutParams:

用来指定视图的高度和宽度

2.2 MeasureSpec(测量规格):

32位int值 , 最高两位表示SpecMode是模式占位符, 后面30位表示测量规格的大小;

在一个空间measure过程中, 会将这个View的LayoutParams结合父容器生成一个MeasureSpec, MeasureSpec就会规定好怎样去测量这个View容器的大小, 返回给父容器, 父容器根据这个去测量大小

模式名称 模式数值 实际数值
UNSPECIFIED 00 000000000000000000001111011000
EXACTLY 01 000000000000000000001111011000
AT_MOST 10 000000000000000000001111011000
UNSPECIFIED: 不确定, 父控件不会对子控件有任何约束, 只要小于手机屏幕宽和高;

EXACTLY: 父容器会对子视图确定一个大小, 无论子视图有多大, 都必须限定在父容器给定的范围内;

AT_MOST : 父容器为所有子视图指定一个最大的尺寸, 子视图所有的大小都必须在这个范围内;

2.3 measure 重要的回调方法:

measure(): 调用onMeasure();
树遍历所有子结点;

onMeasure(): 将所有测量的规格传递给setMeasuredDimension();

1
2
3
4
5
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasuredDimension是用来通知测量结束的, 必须调用
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

setMeasuredDimension(): 完成整个测测量过程;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* <p>This method must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.</p>
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;

measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

注: 如果父视图发现子视图传递的大小可能不对时候, 会再次请求子视图进行测量, 如果给定的数值超过了规定大小或者太小, 父视图会赋值给AT_MOST 或者 EXACTLY的形式,再次对子视图进行测量

3. layout

会根据测量所得到的尺寸来确定layout摆放的位置, 子视图的具体位置是相对于父视图而言的, 必须实现onLayout(), 重新摆放;

layout(): 调用onLayout();

onLayout(): 一定要实现;
可以去分析LinearLayout中的实现;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

是一个树形结构, 依次从ViewGroup进行位置摆放

4. draw

两个比较容易混淆的回调方法:

4.1 invalidate(): 请求安卓系统;

如果视图大小没有发生变化, 则不会调用layout放置过程;

4.2 requestLayout(): 当布局方向变化, 尺寸变化就回去调用;

会触发measure, layout过程, 但不会调用draw方法