View绘制

ViewRoot 和 DecorView

ViewRoot即ViewRootImpl,是连接WindowManager和DecorView的纽带。

View的绘制流程是从ViewRoot的performTraversals开始的,依次进过measurelayoutdraw完成最终的View的绘制。 performTraversals的工作流程 performTraversals会依次调用performMeasureperformLayoutperformDraw方法。


DecorView是顶级View,它是一个LinearLayout的布局,上面就是actionbar,下面就是内容栏。内容栏的id为content。 我们可以通过如下代码得到内容栏的view:

ViewGroup content = (ViewGroup)findViewById(android.R.id.content);

MeassureSpec

MeassureSpec是一个32位的int,分别记录了尺寸(SpecSize)和计算尺寸的测量模式(SpecMode)。

  • UNSPECIFIED:一般是系统使用,父容器不对View做限制,要多大给多大。
  • EXACTLY:对应具体的数值或LayoutParams的match_parent
  • AT-MOST:对应LayoutParams的wrap_content。父容器指定了一个SpecSize的大小,View的大小不能大于这个值。

View的计算MeassureSpec的过程

顶级view——DecorView的过程

DecorView的MeassureSpec是由窗口大小和其自身的LayoutParams来决定的。 ViewRoot的measureHierarchy描述了这个过程,这里还区分了dialog和一般activity页面的差异。 具体实现:

/**
 * Figures out the measure spec for the root view in a window based on it's
 * layout params.
 *
 * @param windowSize
 *     The available width or height of the window
 *
 * @param rootDimension
 *     The layout params for one dimension (width or height) of the
 *            window.
 *
 * @return The measure spec to use to measure the root view.
 */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

普通View

普通View的MeassureSpec是由父容器的MeasureSpec和自身的LayoutParams来决定的。(这个其实跟DecorView逻辑上是一致的)。

主要看ViewGroup中的getChildMeasureSpec方法。

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            ... ...
    }

View工作流程-measure

view的measure过程

view的measure是从measure方法开始的,这个是不可被继承的,但是实际的测量是在onMeasure里完成的,这个方法子类可以重写。

viewGroup的measure过程

measureChildren -> measureChild(只有在子view不为gone时才会调用) -> measure(这里就进入到view的流程了)

如何准确的获得view的宽高?

  • onWindowFocusChanged:窗口获取光标
  • view.post:这个的原理是把runnable放到消息队列的尾部,因为这时前面的都完成了。
  • ViewTreeObserver:
  • 手动读view进行measure:
    • match_parent: 无法计算
    • 具体的值:
      int width = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
      int height = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
      view.measure(width, height);
      
    • wrap_content:
      int width = View.MeasureSpec.makeMeasureSpec((1<<30) - 1, View.MeasureSpec.AT_MOST);
      int height = View.MeasureSpec.makeMeasureSpec((1<<30) - 1, View.MeasureSpec.AT_MOST);
      view.measure(width, height);
      

View工作流程-layout

getMeasuredWidth/getMeasuredHeight 和 getwidth/getHeight

getwidth/getHeight形成于layout过程; getMeasured*形成于meassure过程; 两者一般情况下是相等的。如果重写了layout方法,并且修改了值就不对等了。

View工作流程-draw

  • 绘制背景 --- drawBackground
  • 绘制自己 --- onDraw
  • 绘制子View --- dispatchDraw
  • 绘制装饰等其他(foreground, scrollbars) --- onDrawForeground

特殊方法:setWillNotDraw(),标记此View是否需要绘制。

自定义View的注意点

  1. 重写View或ViewGroup,必须在onMeasure里对wrap_content做特殊处理,未处理时,则wrap_content的效果和match_parent是一样的。
  2. 重写View,需要在draw中对padding属性进行处理,否则不起作用;重写ViewGroup,需要在onMeasureonLayout里对padding和子元素的margin进行处理。
  3. View中有线程或者动画,要及时停止,否则会有可能会造成内存泄露。可在onDetachedFromWindow()onAttachedToWindow()触发线程或动画的停止和启动。

results matching ""

    No results matching ""