View的绘制原理
Android的View绘制都要经过测量,布局和绘制三个步骤
测量
测量过程用来决定View的宽高,父布局提供了限制条件
View.java
1 | public final void measure(int widthMeasureSpec, int heightMeasureSpec) { |
measure()
方法不可被重写,都是通过重写onMeasure()
来完成测量
View.java
1 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
View的onMeasure()
只调用了setMeasuredDimension()
设置测量好的宽高,就是设置了mMeasuredWidth
和mMeasuredHeight
两个变量,并设置了PFLAG_MEASURED_DIMENSION_SET
标志位
View.java
1 | public static int getDefaultSize(int size, int measureSpec) { |
默认实现通过getDeaultSize()
来获得宽高,综合考虑传入的大小和MeasureSpec
布局
View.java
1 | public void layout(int l, int t, int r, int b) { |
也是通过重写onLayout()
来重写布局
View.java
1 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
View的onLayout()
是空的,需要子类实现
View.java
1 | protected boolean setFrame(int left, int top, int right, int bottom) { |
1 | private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) { |
onSizeChanged()
也是由子类实现,大小变化时做一些事
绘制
View.java
1 | public void draw(Canvas canvas) { |
- 绘制背景
- 如有必要,保存图层,绘制边界的渐变效果
- 绘制内容
- 绘制子元素
- 恢复2中保存的图层
- 绘制前景(滚动条等)
通过重写onDraw()
来绘制内容
View.java
1 | protected void onDraw(Canvas canvas) { |
View的onDraw()
是空的,需要子类实现
1 | protected void dispatchDraw(Canvas canvas) { |
也是由子类实现,在子元素被绘制之前进行控制
实例
ViewGroup
ViewGroup是View的子类
ViewGroup没有重写onMeasure()
,使用的是View的默认实现
ViewGroup.java
1 |
|
onLayout()
是个抽象函数,一定要在子类中重写(因为ViewGroup就是用来布局子元素的)
ViewGroup没有重写onDraw()
,所以也是空实现
LinearLayout
LinearLayout.java
1 |
|
根据方向进行测量,以竖直方向为例
LinearLayout.java
1 | void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { |
- 遍历子元素,子元素未设置
weight
则进行测量,否则跳过 - 如果有跳过测量的元素或有剩余的空间,则进行第二次遍历,处理设置
weight
的元素 setMeasuredDimension()
ViewGroup.java
1 | public static int getChildMeasureSpec(int spec, int padding, int childDimension) { |
布局生成MeasureSpec的默认方法,综合考虑父元素传入的MeasureSpec和子元素的LayoutParams
LinearLayout.java
1 |
|
也是根据方向进行布局,以竖直方向为例
LinearLayout.java
1 | void layoutVertical(int left, int top, int right, int bottom) { |
先根据父布局的Gravity来确定竖直方向的位置,然后遍历子元素LayoutParams中的Gravity来确定水平方向的位置并调用子元素的layout()
LinearLayout.java
1 |
|
也是根据方向进行绘制,对mDivider
进行了判断,可见主要就是进行了分割线的绘制,以竖直方向为例
LinearLayout.java
1 | void drawDividersVertical(Canvas canvas) { |
RelativeLayout
RelativeLayout.java
1 |
|
- 如有必要,解析子元素的依赖关系,按顺序保存在全局数组里
- 水平方向处理子元素,应用布局规则,水平测量子元素
- 竖直方向处理子元素,应用布局规则,测量子元素
- 调整居中/右对齐/底部对齐/Gravity
setMeasuredDimension()
RelativeLayout.java
1 |
|
onLayout()
很简单,因为测量的时候每个子元素的边界都保存在布局参数里了,这里直接用它们进行布局
RelativeLayout并没有重写onDraw()
,所以是空实现
FrameLayout
FrameLayout.java
1 |
|
- 遍历测量子元素
setMeasuredDimension()
- 如果有布局参数是
match_parent
的,需要用新的宽度得到MeasureSpec再次测量
FrameLayout.java
1 |
|
遍历子元素,根据Gravity参数布局
FrameLayout也没有重写onDraw()
, 为空实现