UI绘制源码分析_第二弹

2017-11-23  本文已影响0人  Lypop

上一章我们从setContentView为出发点研究了DecorView的创建过程和显示过程,本章将继续进行研究UI的绘制流程

正文

上一章一步一步的走到了performTraversals方法,开启了View的绘制

private void performTraversals() {
    // cache mView since it is used so much below...
    final View host = mView; //将DecorView赋值给了host
    ......//一些简单的赋值
    WindowManager.LayoutParams lp = mWindowAttributes;
    .....
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

     // Ask host how big it wants to be
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);(1)

    int width = host.getMeasuredWidth();
    int height = host.getMeasuredHeight();
    boolean measureAgain = false;
    // lp是否设置了权重,如果设置了则将measureAgain置为true
    if (measureAgain) {
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    layoutRequested = true;
}
    final boolean didLayout = layoutRequested && !mStopped;
    if (didLayout) {
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);(2)
     }
    ......
    boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
            viewVisibility != View.VISIBLE;
    if (!cancelDraw && !newSurface) {
    ......
            performDraw();(3)
    }
}

在方法中分别调用了performMeasure、performLayout、performDraw方法,分别对应测量、布局、绘制。getRootMeasureSpec的作用就是得到根View的测量规格

private static 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(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

要想知道measureSpec的值就需要知道rootDimension是什么,一步步可以查到rootDimension是ViewGroup.LayoutParams.MATCH_PARENT,它是通过mWindowAttributes得到,mWindowAttributes又是WindowManager.LayoutParams的对象

public LayoutParams() {
        super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        type = TYPE_APPLICATION;
        format = PixelFormat.OPAQUE;
 }
performMeasure

所以performMeasure向下传递的是EXACTLY模式的,ok,进入performMeasure方法

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

这里调用了DecorView的measure方法,因为DecorView没有这个方法,它会调用View里面的measure方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {//为View加入阴影的效果
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }
    ......
    if (forceLayout || needsLayout) {
        //这里将测量缓存起来,如果缓存中有的话就不进行测量
        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
    }

ok,因为是第一次测量,所以要进入onMeasure方法,由于上面操作的是DecorView对象,所以在onMeasure方法也是调用DecorView的,因为DecorView不具有代表性,这里我们查看FrameLayout方法的onMeasure

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    for (int i = 0; i < count; i++) {//对ViewGroup进行遍历,分别对子Child进行测量
        final View child = getChildAt(i);
        //当child为空的时候则不走测量的操作
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
        }
    }
    //最后要调用setMeasureDimension来确定ViewGroup的大小,如果不调用则会报错
    //但是平时我们写也不会报错因为我们调用了super.measure()方法,View有一个默认的实现
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
}

ok,进入measureChildWithMargins()方法

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//调用子View的measure的方法,如果子View也为GroupView的时候将重复上述的操作
}

ViewGroup对子View的测量有三个方法分别是measureChildWithMargins、measureChild、measureChildren,进入getChildMeasureSpec方法用来得到ViewGroup传给子View的测量规格

   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) {
    case MeasureSpec.EXACTLY://当父View传过来的mode是Exactly
        //当子View的尺寸是确定值并且大于0时则传给子View的尺寸为Child尺寸,mode为EXACTLY
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        //当child的尺寸为Match_parent的时候则将传给子View的尺寸为父View的尺寸,mode为EXACTLY
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        //当Child的尺寸为Wrap_content的时候则传给子View的尺寸为父View的尺寸,mode为AT_MOST
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    ......下面的类似,不多说
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

tips:

  1. EXACTLY :父容器已经测量出所需要的精确大小,这也是childview的最终大小 match_parent,精确值
  2. ATMOST : child view最终的大小不能超过父容器的给的 wrap_content
  3. UNSPECIFIED: 不确定,源码内部使用 一般在ScrollView,ListView

ok,View的测量方法就完了,performLayout、performDraw套路是一样的

我们在自定义View或者ViewGroup的时候不要在onMeasure方法里面用getWidth/getHeight/getMeasureWidth得到View的尺寸,因为在measure的最后会调用View的setMeasuredDimensionRaw方法

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

所以只能在onMeasure方法之后使用getMeasureWidth方法,另外getWidth/getHeight则应该在onLayout方法之后

@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
    return mRight - mLeft;
}

所以当布局确定了之后才能得到View的尺寸。

对上面来个总结:

当我们使用自定义View的时候最终调用setMeasuredDimession方法来保存自己的测量宽高

final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize =  MeasureSpec.getSize(measureSpec);
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            /* Parent says we can be as big as we want. Just don't be larger
               than max size imposed on ourselves.
            */
            result = Math.min(desiredSize, maxSize);
            break;
        case MeasureSpec.AT_MOST:
            // Parent says we can be as big as we want, up to specSize.
            // Don't be larger than specSize, and don't be larger than
            // the max size imposed on ourselves.
            result = Math.min(Math.min(desiredSize, specSize), maxSize);
            break;
        case MeasureSpec.EXACTLY:
            // No choice. Do what we are told.
            result = specSize;
            break;
    }
    return result;

当我们使用ViewGroup的时候

  1. 测量子view的规格大小measureChildWithMargins、measureChild、measureChildren
  2. 通过子view的规格大小来确定自己的大小 setMeasuredDimession

通过上面的学习,我们可以做出自己的流式布局,效果如下:


TIM截图20171123120056.png
  1. 获取父容器设置的测量模式和大小

     int iWidthMode = MeasureSpec.getMode(widthMeasureSpec);
     int iHeightMode = MeasureSpec.getMode(heightMeasureSpec);
     int iWidthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
     int iHeightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    
  2. 核心代码

      if(iCurLineW + iChildWidth > iWidthSpecSize){
             //记录当前行的最大宽度,高度累加
             measuredWith = Math.max(measuredWith,iCurLineW);
             measuredHeight += iCurLineH;
             //将当前行的viewList添加至总的mViewsList,将行高添加至总的行高List
             mViewLinesList.add(viewList);
             mLineHeights.add(iCurLineH);
    
             iCurLineW = iChildWidth;
             iCurLineH = iChildHeight;
    
             viewList = new ArrayList<View>();
             viewList.add(childView);
         }else{
             iCurLineW += iChildWidth;
             iCurLineH = Math.max(iCurLineH, iChildHeight);
             viewList.add(childView);
         }
    
  3. 最后一行需要进行换行操作

     if(i == childCount - 1){
         measuredWith = Math.max(measuredWith,iCurLineW);
         measuredHeight += iCurLineH;
    
         mViewLinesList.add(viewList);
         mLineHeights.add(iCurLineH);
     }
    
  4. 设置最终的大小

     setMeasuredDimension(measuredWith,measuredHeight);
    
  5. 对子View进行布局

     int cusTop = 0;
     int cusLeft = 0;
     for (int i = 0; i < totalViews.size(); i++) {
         List<View> lineViews = totalViews.get(i);
         int lineHeight = mLineHeights.get(i);
         for (int j = 0; j < lineViews.size(); j++) {
             View childView = lineViews.get(j);
             MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
             int childLeft = cusLeft + layoutParams.leftMargin;
             int childTop = cusTop + layoutParams.topMargin;
             int childRight = childLeft + childView.getMeasuredWidth();
             int childBottom = childTop + childView.getMeasuredHeight();
             childView.layout(childLeft, childTop, childRight, childBottom);
             cusLeft += childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
         }
         cusLeft = 0;
         cusTop += lineHeight;
     }
    

凡不能毁灭我的,必使我强大

共勉~

上一篇 下一篇

猜你喜欢

热点阅读