Android View 的工作原理
一、基础知识
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);
}
|