Android View 绘制过程解析

   学习过window编程的人都知道,在屏幕上绘制视图控件很原理很简单,指定屏幕上一块区域,在里面去绘制你想要的内容,其他的系统实现原理也大同小异,但真正实现起来,还是相当复杂的。这几天看了下Android 的View的代码,觉得里面内容还是相当多的,把自己理解学习的再此罗列一下,大家共同交流学习。

    屏幕的绘制可以理解为由类ViewRoot(4.0版本以前)或ViewRootImpl(4.0版本以后)的方法performTraversals()触发。在这个方法内主要依次做了三件事:

1. measure  计算自己大小尺寸,如果为ViewGroup,递归调用容器内view的measure。

2. layout  设置自己在父容器内的位置,即设置成员变量mLeft,mTop,mRight,mBottom,如果为ViewGroup,依次调用容器内的View的layout

3.draw  在自己的区域内绘制自己

 

 private void performTraversals(){

      final View host = mView;//mView为PhoneWindow.DecorView

              ......

      childWidthMeasureSpec = getRootMeasureSpect(desiredWindowWidth, lp.width);

      childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

      host.measure(childWidthMeasureSpec, childHeightMeasureSpec);

      ......

     host.layout(0, 0, host.mMeasureWidth, host.mMeasureHeight);

      ......

    draw(fullRedrawNeeded);

    ......

}

 

下面分别介绍measure(),layout(),draw()三个过程的实现。

一、measure

     public final void measure(int , int )方法主要是计算View的宽和高,通过方法setMeasuredDimension(int, int)给成员变量mMeasuredWidth和mMeasuredHeight赋值。如果View是ViewGroup类型,则分别调用它容器内View的measure方法,递归的计算出每个View的宽和高。

measure的运算结果由父容器和本身决定,下面的部分代码显示ViewRoot中measure(int, int)两个参数值的运算过程

 

childWidthMeasureSpec = getRootMeasureSpect(desiredWindowWidth, lp.width);

      childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

      host.measure(childWidthMeasureSpec, childHeightMeasureSpec);

 

private int getRootMeasureSpec(int windowSize, int rootDimension){
    int measureSpec;
    switch(rootDimension){
        case ViewGroup.LayoutParams.MATCH_PARENT:
           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
           break;
       case ViewGroup.LayoutParams.WRAP_CONTENT:
           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
           break;
      default:
          measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
          break;
   }
   return measureSpec;
}

 

 public static int makeMeasureSpec(int size, int mode) {
            return size + mode;
 }

 

而一个View的尺寸最终是由setMeasuredDimension(int, int)决定的,也就是说如果你在这里传了(100,100),无论使用view的xml中或者代码中怎么给它传值,它的尺寸都是100x100。

这里联想到一个有趣的问题:ViewGroup的值设为wrap_content,而它里面的view设置为match_parent,那么它们measure后的值应该是怎么样的?看了下FrameLayout的measure实现,容器在算它的尺寸时,会先把里面match_parent的View缓存起来,根据非match_parent的View计算出它的尺寸,然后再把它的尺寸做为参数,让match_parent的view算自己的尺寸。

二、layout

    layout主要是给view的成员变量mLeft,mTop,mRight,mBottom赋值,这里表示相对于父容器的位置。ViewGroup通过onLayout方法,给它容器内的子view调用layout()方法赋值,而view通常不需要实现onLayout。
 

三、draw

    draw同样是个比较复杂的过程,但原理和上面差不多,每个view会通过draw在自己的区域画出自己,而ViewGroup会通过方法dispatchDraw()遍历所有子view,使子view调用自己的draw()方法绘制自己。

你可能感兴趣的:(Android View 绘制过程解析)