对于自定义View,其绘制的基本步骤为measure(测量),layout(布局),draw(绘制)三个阶段。而ViewRootImpl作为连接view和android系统的重要组成部分(ViewRootImpl 类,是连接 WindowManager 和 DecorView 的纽带),在这里不得不提。我们可以把ViewRootImpl的根,View是在ViewRootImpl的基础上建立起来的。下面以一幅图作为理解
ViewRootImpl层 preformMeasure()、preformLayout()、preformDraw()
ViewRootImpl层 mView.measure()、mView.layout()、draw()(直接在ViewRootImpl中被引用)
用户层Onmeasure()、OnLayout()、OnDraw()方法。
其中preformMeasure调用了measure(),measure()调用了Onmeasure()其他两者也是这种情况。
PS:layout()除了调用onLayout()外还调用了了onMeasure().
measure()方法有两种典型的调用方式。
在ViewRootImpl类中被调用:
mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
在ViewGroup中被调用:
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
从上面这两种调用方式,我们可以知道measure()主要用来测量子容器的大小,其中的参数由父容器来决定。(ViewGroup是child的父容器,ViewRootImpl可以看成是View的父容器)。
对于ViewRootImp中调用measure方法的情况,我们可以通过其源码来分析:
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
//doTraversal()方法中调用了performtraversal()
void doTraversal() {
...
performtraversal();
...
}
对performtraversal()方法的分析至关重要。
private void performTraversals() {
//1372行 Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
...
//1428行
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
//1767行
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//1793行
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//1829行
performLayout();
//1968行
performDraw();
}
其中measureHierarchy方法中多次调用了performMeasure方法,而且在performLayout中被调用。
也就是performLayout调用measureHierarchy,measureHierarchy调用了performMeasure.
从上面可以看出,绘制View时,遵循了先测量后布局再绘制的原则。
下面的部分为自己下篇文章所要深入探讨的问题,也就是DecorView
对于 DecorView ,其 MeasureSpec 由窗口的尺寸和其自身的LayoutParams 来共同确定
对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定
google 官方Demo
CustomView.java
大概思路是这样的:PieChart继承自ViewGroup,这个ViewGoup容器中有PieView和PointerView。画图步骤如下:为了简化画图流程,以固定layout_width = 200dp , layout_height = 200dp为例来进行说明,其实WARP_CONTENT和FILL_PARENT情况差不多,只是由于需要先遍历子View之后才能确定ViewGroup大小,稍微复杂一点。
- PieChart构造函数中初始化PieView和PointerView并addview。
- PieChart调用Onmeasure方法测量尺寸。
- 这时,PieChart的尺寸变化了,就会自动调用PieChart的onSizeChanged()方法。在onSizeChanged()方法中调用了mpieView.layout()方法,由于layout()方法用于将子View布局到PieChart上,这时候pieView将登场
- 在PieView绘制的过程中,onMeasure()之后就是onSizeChanged(),在调试的过程中发现没有进入onMeasure方法,不知道为什么。
- 待PieView在PieChart上布局完之后(PieView本身的OnDraw方法还没有执行),PieChart继续未完成的工作,OnLayout,OnDraw
- PieChart执行完一次完整绘图之后,子View(PieView)就接着完成未完成的任务onDraw()
所以总结为先是ViewGroup测量,然后在ViewGroup的onSizeChanged方法 中布局PieView,调用了PieView.layout(l,t,r,b)方法(也就是调用了OnLayout()),这样PieView的尺寸就会发生变化(4个坐标轴的值由0变成l,t,r,b)。子View布局完成之后,ViewGroup开始布局OnLayout()和绘图OnDraw()。打印结果如下:
04-06 20:25:53.646 12384-12384/com.example.tony.recyclerview E/PieView﹕ ----PieView()-----
04-06 20:25:53.646 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------Before addView
04-06 20:25:53.647 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------after addview
04-06 20:25:53.672 12384-12384/com.example.tony.recyclerview E/PeChart﹕ ------onMeasure----------
04-06 20:25:53.712 12384-12384/com.example.tony.recyclerview E/PeChart﹕ ------onMeasure----------
04-06 20:25:53.712 12384-12384/com.example.tony.recyclerview E/PieChart﹕ onSizeChanged w = 400
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ onSizeChanged h = 400
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ onSizeChanged oldw = 0
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ onSizeChanged oldh = 0
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------before mPieView.layout()
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieView﹕ -----onSizeChanged----
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieView﹕ onsizechanged w = 360
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieView﹕ onsizechanged h = 360
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieView﹕ ---------onLayout----
04-06 20:25:53.713 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------after mPieView.layout()
04-06 20:25:53.714 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onLayout------------
04-06 20:25:53.724 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onDraw------------
04-06 20:25:53.736 12384-12384/com.example.tony.recyclerview E/PieView﹕ -----onDraw----
04-06 20:25:53.784 12384-12384/com.example.tony.recyclerview E/PeChart﹕ ------onMeasure----------
04-06 20:25:53.784 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onLayout------------
04-06 20:25:53.787 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onDraw------------
04-06 20:25:53.801 12384-12384/com.example.tony.recyclerview E/PieView﹕ -----onDraw----
04-06 20:25:54.064 12384-12384/com.example.tony.recyclerview E/PieChart﹕ ------onDraw------------
04-06 20:25:54.077 12384-12384/com.example.tony.recyclerview E/PieView﹕ -----onDraw----
根据对draw(Canvas c)源码分析发现,绘图的过程可以分成5步,其中重要的的步骤有
- draw the backgroud if needed
- draw the content(在这里会调用OnDraw())
- draw the children(在这里会调用子View的draw()方法,也就会调用相应的OnDraw()方法)
- draw the decorations(foregroud,scrllbars)
所以上面的调用顺序就不难理解了。上面的分析还有一个问题:子view在布局的时候为什么没有进入OnMeasure方法,不是很理解