您的位置:首页 > 移动开发 > Android开发

Android View总结

2015-12-25 14:12 645 查看


关于Android View控件

Android中控件大致被分为两类ViewGroup,View。ViewGroup作为容器管理View。Android视图,是类似于Dom树的架构。父视图负责测量定位绘制等操作。我们经常在用的
findViewById
方法代价昂贵的原因,就是因为他负责至上而下遍历整棵控件树,来寻找View实例,在重复操作中尽量少用。现在在用的很多控件都是直接或者间接继承自View的,如下图。






Android UI界面架构

每个Activity包含一个
PhoneWindow
对象,
PhoneWindow
设置
DecorView
为应用窗口的根视图。在里面就是熟悉的
TitleView
ContentView
,没错,平时使用的
setContentView()
就是设置的
ContentView






Android是如何绘制View的?

当一个Activity启动时,会被要求绘制出它的布局。Android框架会处理这个请求,当然前提是Activity提供了合理的布局。绘制从根视图开始,从上至下遍历整棵视图树,每一个
ViewGroup
负责让自己的子
View
被绘制,每一个
View
负责绘制自己,通过
draw()
方法.绘制过程分三步走。

Measure
Layout
Draw

整个绘制流程是在
ViewRoot
中的
performTraversals()
方法展开的。部分源代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

private void performTraversals() {
......
//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
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);
......
}

在绘制之前当然要知道view的尺寸和绘制。所以先进行
measu
layout
(测量和定位),如下图。





Measure过程

1
2
3
4
5
6
7
8

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//....

//回调onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);

//more
}

计算view的实际大小,获得高宽存入
mMeasuredHeight
mMeasureWidth
measure(int,
int)
传入的两个参数。
MeasureSpec
是一个32位int值,高2位为测量的模式,低30位为测量的大小。测量的模式可以分为以下三种。

EXACTLY

精确值模式,当
layout_width
layout_height
指定为具体数值,或者为
match_parent
时,系统使用EXACTLY。

AT_MOST

最大值模式,指定为
wrap_content
时,控件的尺寸不能超过父控件允许的最大尺寸。

UNSPECIFIED

不指定测量模式,View想多大就多大,一般不太使用。

根据上面的源码可知,measure方法不可被重写,自定义时需要重写的是
onMeasure
方法

1
2
3
4

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

查看源码可知最终的高宽是调用
setMeasuredDimension()
设定的,如果不重写,默认是直接调用
getDefaultSize
获取尺寸的。

使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。


Layout过程

Layout方法就是用来确定view布局的位置,就好像你知道了一件东西的大小以后,总要知道位置才能画上去。

mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());


layout获取四个参数,左,上,右,下坐标,相对于父视图而言。这里可以看到,使用了刚刚测量的宽和高。
1
2
3
4
5
6
7
89
10
11

public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
.....
onLayout(changed, l, t, r, b);
.....
}

通过
setFrame
设置坐标。如果坐标改变过了,则重新进行定位。如果是View对象,那么onLayout是个空方法。因为定位是由ViewGroup确定的。

当layout结束以后
getWidth()
getHeight()
才会返回正确的值。

这里出现一个问题,
getWidth/Height()
and
getMeasuredWidth/Height()
有什么区别?

getWidth()
:View在設定好佈局後整個View的寬度。
getMeasuredWidth()
:對View上的內容進行測量後得到的View內容佔據的寬度





Draw过程

1
2
3
4
5
6
7
8
9
10
11
12
13
1415
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

public void draw(Canvas canvas) {
......
/*
* 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
......
if (!dirtyOpaque) {
drawBackground(canvas);
}

// skip step 2 & 5 if possible (common case)
......

// Step 2, save the canvas' layers
......
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
......

// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

// Step 5, draw the fade effect and restore layers
......
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
......

// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
......
}

重点是第三步调用onDraw方法。其它几步都是绘制一些边边角角的东西比如背景、scrollBar之类的。其中
dispatchDraw
,是用来递归调用子View,如果没有则不需要。

onDraw方法是需要自己实现的,因为每个控件绘制的内容不同。主要用canvas对象进行绘制,这里就不说了。


参考资料

Android视图绘制流程完全解析,带你一步步深入了解View(二)
Android应用层View绘制流程与源码分析
How
Android Draws Views
《Android群英传》
What
is the difference between getWidth/Height() and getMeasuredWidth/Height() in Android SDK?

点击打开链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: