onMeasure onLayout onDraw

对于自定义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大小,稍微复杂一点。

  1. PieChart构造函数中初始化PieView和PointerView并addview。
  2. PieChart调用Onmeasure方法测量尺寸。
  3. 这时,PieChart的尺寸变化了,就会自动调用PieChart的onSizeChanged()方法。在onSizeChanged()方法中调用了mpieView.layout()方法,由于layout()方法用于将子View布局到PieChart上,这时候pieView将登场
  4. 在PieView绘制的过程中,onMeasure()之后就是onSizeChanged(),在调试的过程中发现没有进入onMeasure方法,不知道为什么。
  5. 待PieView在PieChart上布局完之后(PieView本身的OnDraw方法还没有执行),PieChart继续未完成的工作,OnLayout,OnDraw
  6. 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方法,不是很理解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值