控件的测量,布局,绘制过程
目录:
- 测量
1. 测量之MeasureSpec与LayoutParams
2. View # onMeasure(int,int)
3. ViewGroup # measureChildren()
4. 测量某个View的宽高的方式
- 布局
1. View # layout(int,int,int,int)
2. View # onLayout()
1. LinearLayout
2. RelativeLayout
3. FrameLayout
- 绘制
View # draw(Canvas)
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) 绘制附加内容
参考自:
1. 《 Android 开发艺术探索 》 : Page 174
2. Android开发之自定义控件(二)---onLayout详解 : http://blog.csdn.net/dmk877/article/details/49632959
测量
1. 测量之MeasureSpec
与LayoutParams
WindowManager$LayoutParams
友情链接 点击此处
《 Android 开发艺术探索 》
对于DecorView
,其MeasureSpec
由窗口的尺寸和其自身的LayoutParams
来共同决定;
对于普通View
,其MeasureSpec
由 父容器 的MeasureSpec
和自身的LayoutParams
来共同决定。parentSize
指父容器当前剩余的空间大小。
1 |
|
2. View # onMeasure(int,int)
MeasureSpec.getSize(measureSpec)
就是View
测量后的大小,在layout()
阶段确定;getSuggestedMinimumWidth/Height()
设置默认大小:
- 当未设置背景时,由
android:minWidth/minHeight
决定;- 当已设置背景时,由背景Drawable原始大小最小值与
android:minWidth/minHeight
决定;- 依照上表, 当自定义
View
直接继承自View
重写onMeasure()
并设置wrap_content
时,相当于直接填充了父容器剩余所有空间match_parent
。可以通过onMeasure()
设置宽高默认值解决
1 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
3. ViewGroup # measureChildren()
1 |
|
4. 测量某个View的宽高的方式
Activity/View # onWindowFocusChanged(boolean)
1
2
3
4
5
6
7
8
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
//measureLines();
}
}View # post(Runnable)
1
2
3
4
5
6
7
8
9
view.post(new Runnable() {
public void run() {
view.getWidth(); // 获取宽度
view.getHeight(); // 获取高度
}
});ViewTreeObserver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17view.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= 16) {
view.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
}
else {
view.getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
view.getWidth(); // 获取宽度
view.getHeight(); // 获取高度
}
});View # measure(int,int)
设置固定数值
1
2
3
4
5
6
int width = View.MeasureSpec.makeMeasureSpec(100,
View.MeasureSpec.EXACTLY);
int height = View.MeasureSpec.makeMeasureSpec(100,
View.MeasureSpec.EXACTLY);
view.measure(width, height);wrap_content
1
2
3
4
5
6
int width = View.MeasureSpec.makeMeasureSpec((1<<30)-1,
View.MeasureSpec.AT_MOST);
int height = View.MeasureSpec.makeMeasureSpec((1<<30)-1,
View.MeasureSpec.AT_MOST);
view.measure(width, height);
布局Layout
《 Android 开发艺术探索 》:
ViewGroup
用来确定子元素的位置,layout()
确定的是ViewGroup
本身的位置,而onLayout()
确定的是所有子控件的位置;.
onLayout详解 : 自定义控件的第二步onLayout
即确定控件的位置
1. layout(int,int,int,int)
主要有以下步骤:
View # setFrame(l, t, r, b)
初始化mLeft、mRight、mTop、mBottom,设置当前View/ViewGroup
的四个顶点的位置;View # onLayout(changed, l, t, r, b)
确定子控件位置;- 判断
viewGroup.mOnLayoutChangeListeners
的存在,并调用View $ OnLayoutChangeListener # onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB)
;
1 |
|
2. View # onLayout()
以 LinearLayout、RelativeLayout、FrameLayout 为例,代码如下.
LinearLayout
以竖直方向为例,由于 LinearLayout
特性,当设置竖直方向时,循环得到子控件的mTop(childTop,包含marginTop)
, 根据模拟测量宽高调用setChildFrame(...)
间接调用child.layout(...)
为子控件指定对应的位置; 然后继续叠加mTop
.
1 |
|
RelativeLayout
看似很简单的操作.更多的操作已经在初始化或在onMeasure()
中已经完成,例如在onMeasure()
中子控件之间的”toLeftOf
“等相关view,已经存储入对应数组,并在循环中调用 applyHorizontalSizeRules(params, myWidth, rules)
对子控件位置信息进行设置;
1 |
|
FrameLayout
与RelativeLayout
相比,多了权重的判断,少了关系判断.
1 |
|
Draw
绘制操作代码如下.
1 |
|
1. 绘制背景
1 |
|
2. 保存画布用于⑤绘制渐变
1 |
|
3. 绘制内容
1 |
|
4. 绘制子控件
步骤:
- 设置子控件的LayoutAnimation并设置动画监听;
- 判断
clipToPadding
,是否裁剪; - 循环调用子控件绘制
drawChild(canvas, child, drawingTime)
,保存裁剪信息; - 判断动画是否结束并回调动画结束函数;
1 |
|
5. 绘制渐变
1 |
|
6. 绘制装饰附加内容
1 |
|