一、基础知识
1、ViewRoot 和 DecorView
ViewRoot 对应 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程都是通过 ViewRoot 来完成的。在ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRoot 对象。
DecorView 添加到窗口 Window 的过程。
View 的绘制流程从 ViewRootImpl 的 preformTraversals 开始,下面是它的伪码
1
2
3
4
5
6
7
8
|
// ViewRootImple#performTraverals 的伪代码
private
void
preformTraverals(){
preformMeasure(...) --------- View.measure(...);
performLayout(...) --------- View.layout(...);
performDraw(...) -------- View.draw(...);
}
|
2、 MeasureSpec
二、View 的工作流程
1、measure 过程
.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
该方法就是我们只自定义 View 的时候需要实现测量绘制逻辑的方法,该方法的参数是父视图对子视图的 widht 和height 测量方法的要求。在自定义 View 时,需要做的就是更加 widthMeasureSpec 和 heightMeasureSpec 计算View 的width 和 height ,不同的处理模式不同。
.setMeasuredDimension(int measuredWidth, int measureHeith)
测量阶段的终极方法,在 onMeasure(int widthMeasureSpec, int heightMeasureSpece) 方法中调用,将计算的得到尺寸传递给该方法,测量阶段结束。该方法必须调用,否则会报异常。在自定义 View 的时候,不需要关系系统复杂的 Measure 过程,只需调用setMeasuredDimension(int measuredWith, int measuredHeith) 设置根据 MeasureSpec计算得到的尺寸即可。
(2)measure 过程
Measure 过程传递尺寸的两个参数
ViewGroup.LayoutParams View 自身的布局参数;
MeasureSpec 类, 父视图对子视图的测量要求。
View 的 measure 过程
View 的 getDefaultSize 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// View#getDefaultSize
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 的自定义控件需要重写 onMeasure(...) 方法并设置 wrap_content 时的自身大小,否则在布局中使用 wrap_content 相当于使用 match_parent. 从 MeasureSpece 和 LayoutParams 关系表格中可看出。
解决方法,给 View 指定一个默认的内部宽高(mWith 和 mHeight),并在 wrap_content 时设置此宽高即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec){
super
.onMeasure(widthMeasureSpec, heightMeasureSpec);
int
widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int
heightSpeceMode = MeasureSpec.getMode(heightMeasureSpec);
int
widthSpeceSize = View.MeasureSpec.getSize(widthMeasureSpec);
int
heightSepceSize = View.MeasureSpec.getSize(heightMeasureSpec);
if
(widthMeasureSpec == MeasureSpec.AT_MOST
&& heightMeasureSpec == MeasureSpec.AT_MOST){
// 设置一个默认的宽高
setMeasuredDimension(mWidth, mHeight);
}
else
if
(widthSpecMode == View.MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, heightSepceSize);
}
else
if
(heightSpeceMode == View.MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpeceSize, mHeight);
}
}
|
ViewGroup 的 measure 过程.
ViewGroup 除了完成自己的 measure 过程以为,还会遍历去调用所有子元素的 measure 方法。 ViewGroup 是一个抽象类,没有重写 View 的 onMeasure 方法。ViewGroup 也没有定义其测量的具体过程,其测量过程的 onMeasure 方法需要各个之类去实现。
measure 完成以后,可以通过 getMeasureWidth / getMeasureHeight 获取 View 的测量宽高, 要在 onLayout 方法中去获取 View 的测量宽高或者最终宽高。
因为 View 的 measure 过程和 Activity 的生命周期方法不是同步的,因此无法保证 Activity 在 onCreate, onStart, onResume 方法中获取 View 的宽高信息。
解决办法:
1. 在 Activity/View # onWindowFoucsChanged 方法中
1
2
3
4
5
6
7
8
|
@Override
public
void
onWindowFocusChanged(
boolean
hasFocus) {
super
.onWindowFocusChanged(hasFocus);
if
(hasFocus){
int
width = view.getMeasuredWidth();
int
height = view.getMeasuredHeight();
}
}
|
2. 使用 view.post(Runnable)
1
2
3
4
5
6
7
8
9
10
11
|
@Override
protected
void
onStart() {
super
.onStart();
mTextView.post(
new
Runnable() {
@Override
public
void
run() {
int
width = view.getMeasuredWidth();
int
height = view.getMeasuredHeight();
}
});
}
|
3.使用 ViewTreeObserver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Override
protected
void
onStart() {
super
.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(
new
ViewTreeObserver.OnGlobalLayoutListener() {
@SuppressWarnings
(
"deprecation"
)
@Override
public
void
onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(
this
);
int
widht = view.getMeasuredWidth();
int
height = view.getMeasuredHeight();
}
});
}
|
4. 使用 View.measure(int widthMeasureSpec, int heightMeasureSpec)
通过手动对 View 进行 measure 得到 View 的宽高。
View 的 LayoutParams 分:
match_parent:
无法测出;
具体数值(dp/px):
例如宽高都是 100px 时
1
2
3
|
int
widthMesureSpec = View.MeasureSpec.makeMeasureSpec(
100
, View.MeasureSpec.EXACTLY);
int
heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
100
, View.MeasureSpec.EXACTLY);
view.measure(widthMesureSpec, heightMeasureSpec);
|
wrap_parent 时
1
2
3
|
int
widthMesureSpec = View.MeasureSpec.makeMeasureSpec((
1
<<
30
) -
1
, View.MeasureSpec.AT_MOST);
int
heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((
1
<<
30
) -
1
, View.MeasureSpec.AT_MOST);
view.measure(widthMesureSpec, heightMeasureSpec);
|
2. layout 过程
子视图的具体位置是相对于父视图而言的。View 的 onLayout 方法时空方法,ViewGrop 的 onLayout 方法时 abstract .
如果自定义的 View 继承 ViewGroup ,需要实现 onLayout 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// View#layout
public
void
layout(
int
l,
int
t,
int
r,
int
b) {
if
((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) !=
0
) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int
oldL = mLeft;
int
oldT = mTop;
int
oldB = mBottom;
int
oldR = mRight;
// setOpticalFrame / setFrame 设定 View 的四个顶点
boolean
changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if
(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 空方法
onLayout(changed, l, t, r, b);
...
}
...
}
|
getMeasureWidth 和 getWidth 之间的区别:
getMeasureWidth 是 measure() 过程之后获取后,getWidth 是在 layout() 过程之后得到的。getMeasureWidth() 方法中的值是通过 setMeasureDimension() 方法类进行设置的,而 getWidth() 方法中的值是通过视图右边的坐标减去左边的坐标计算出来的。
3.draw 过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
// View#draw
public
void
draw(Canvas canvas) {
final
int
privateFlags = mPrivateFlags;
final
boolean
dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo ==
null
|| !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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
int
saveCount;
if
(!dirtyOpaque) {
// 第一步,绘制背景
drawBackground(canvas);
}
// 正常情况下,跳过第二步和第五步
// skip step 2 & 5 if possible (common case)
final
int
viewFlags = mViewFlags;
boolean
horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) !=
0
;
boolean
verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) !=
0
;
if
(!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
// 第三步, 绘制自身内容
if
(!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
// 第四不,绘制子元素
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if
(mOverlay !=
null
&& !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
// 第六步, 绘制 foreground, scrollbars
onDrawForeground(canvas);
// we're done...
return
;
}
}
|
setWillNotDraw
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public
void
setWillNotDraw(
boolean
willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW :
0
, DRAW_MASK);
}
|