转载处:http://blog.csdn.net/qinjuning
前言: 本文是我读《Android内核剖析》第13章----View工作原理总结而成的,在此膜拜下作者 。同时真挚地向渴望了解
Android
框架层的网友,推荐这本书,希望你们能够在Android开发里学到更多的知识 。
整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为
根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘
(draw),其框架过程如下:
步骤其实为host.layout()
接下来温习一下整个View树的结构,对每个具体View对象的操作,其实就是个递归的实现。
关于这个 DecorView 根视图的说明,可以参考我的这篇博客:
《Android中将布局文件/View添加至窗口过程分析 ---- 从setContentView()谈起》
流程一: mesarue()过程
主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:
mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
具体的调用链如下:
ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调
View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:
mMeasuredHeight)和宽(对应属性:mMeasureWidth) ;
2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。
2.1 对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去
实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡
层更简单的做法是直接调用View对象的measure()方法)。
整个measure调用流程就是个树形的递归过程
measure函数原型为 View.java 该函数不能被重载
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
- //....
- //回调onMeasure()方法
- onMeasure(widthMeasureSpec, heightMeasureSpec);
- //more
- }
为了大家更好的理解,采用“二B程序员”的方式利用伪代码描述该measure流程
- //回调View视图里的onMeasure过程
- private void onMeasure(int height , int width){
- //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)
- //1、该方法必须在onMeasure调用,否者报异常。
- setMeasuredDimension(h , l) ;
- //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程
- int childCount = getChildCount() ;
- for(int i=0 ;i<childCount ;i++){
- //2.1、获得每个子View对象引用
- View child = getChildAt(i) ;
- //整个measure()过程就是个递归过程
- //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都
- measureChildWithMargins(child , h, i) ;
- //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下:
- //child.measure(h, l)
- }
- }
- //该方法具体实现在ViewGroup.java里 。
- protected void measureChildWithMargins(View v, int height , int width){
- v.measure(h,l)
- }
流程二、 layout布局过程:
主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
具体的调用链如下:
host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下
1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)
接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;
2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。
layout函数原型为 ,位于View.java
- /* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴
- * @param l Left position, relative to parent
- * @param t Top position, relative to parent
- * @param r Right position, relative to parent
- * @param b Bottom position, relative to parent
- */
- public final void layout(int l, int t, int r, int b) {
- boolean changed = setFrame(l, t, r, b); //设置每个视图位于父视图的坐标轴
- if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
- }
- onLayout(changed, l, t, r, b);//回调onLayout函数 ,设置每个子视图的布局
- mPrivateFlags &= ~LAYOUT_REQUIRED;
- }
- mPrivateFlags &= ~FORCE_LAYOUT;
- }
同样地, 将上面layout调用流程,用伪代码描述如下:
- // layout()过程 ViewRoot.java
- // 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout()
- private void performTraversals(){
- //...
- View mView ;
- mView.layout(left,top,right,bottom) ;