Android View绘制流程(源码 API27)

ViewGroup继承自View,ViewGroup是一个包含View的容器。

接口ViewManager里有addView、updateViewLayout、removeView方法,添加、更新、移除方法。

同时ViewGroup是个抽象类,不能直接使用,常用的子类有LinearLayout、relativeLayout、constrainstLayout、frameLayout、CoordinatorLayout等。

public abstract class ViewGroup extends View implements ViewParent, ViewManager 
复制代码

我们自定义View时,往往需要重写onMeasure()、onDraw()

自定义ViewGroup时,往往需要重写onMeasure()、onLayout()、onDraw()

这里我们来分析下View的绘制流程。

ViewRootImpl#performTraversals()

  • //执行测量过程,performMeasure() -> view.measure()
  • //执行布局过程,performLayout() -> view.layout()
  • //执行绘制过程,performDraw() -> view.draw()
private void performTraversals() {

                    WindowManager.LayoutParams lp = mWindowAttributes;
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); //
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                    //执行测量过程
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    //执行布局过程
                     performLayout(lp, mWidth, mHeight);
                    //执行绘制过程
                     performDraw();
}
复制代码

ViewRootImpl#getRootMeasureSpec() 根据

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;
    }
复制代码

ViewRootImpl#performMeasure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {

            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
复制代码

ViewRootImpl#performLayout()

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mInLayout = true;
        final View host = mView;

            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        mInLayout = false;
    }
复制代码

ViewRootImpl#performDraw()

private void performDraw() {
 
        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;

        try {
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

    }
复制代码

ViewRootImpl#draw()

private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
        }

    }
复制代码

ViewRootImpl#drawSoftware()

 private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

        final Canvas canvas;
            canvas = mSurface.lockCanvas(dirty);
            canvas.translate(-xoff, -yoff);
            mView.draw(canvas);
        return true;
    }
复制代码

MeasureSpec

MeasureSpec是View中的一个子类,代表的是测量规格:32位的int类型,高两位是mode,低30位是size

Mode有三种状态:

  • UNSPECIFIED :父控件没有给子view任何限制,子View可以设置为任意大小。linearLayout/scrollView (随心所欲)
  • EXACTLY:父View有确切的大小,子View (match_parent、dp/px)必须是这个范围内 (固定大小)
  • AT_MOST:父View为子view指定了一个范围,在这个范围内,子View可以尽可能的大,wrap_content(受限制的)
 public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT; // 11 0000000000000(后接30个0)

        public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 00 000000000000000(后接30个0)
        public static final int EXACTLY     = 1 << MODE_SHIFT; // 01 000000000000000(后接30个0)
        public static final int AT_MOST     = 2 << MODE_SHIFT; // 10 000000000000000(后接30个0)


        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode; //二进制的加法
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        //得到测量模式
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        //得到测量尺寸
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    }
复制代码

View#measure()

**View#measure()**是final类型,子类不能重写,自定义View/ViewGroup往往重写的是onMeasure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        //如果和父类的optical bounds不一样,则需要更新下widthMeasureSpec和heightMeasureSpec
                onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
复制代码

View#onMeasure()

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
复制代码

View#setMeasuredDimension() final类型,子类不能重写

 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        //还是与那个optical bounds有关,如果自己和父类不一致,可能会更新measuredWidth,measuredHeight
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
复制代码

View#setMeasuredDimensionRaw() 给mMeasuredWidth、mMeasuredHeight赋值

 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;
    }
复制代码

View#getDefaultSize()

measureSpec 是由父类传递给子类的测量规格,int类型,32个bit,高两位是模式,低30是尺寸。

  • 如果父类是specMode是unspecified(随你所想):则自己取size
  • 如果父类的specMode是at_most(受限)或者exactly(固定尺寸),则自己取specSize
public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
复制代码

View#getSuggestedMinimumWidth()

mBackground.getMinumWidth()返回背景图Drawable的原始宽度,若无原始宽度,则为0;

  • 注:BitmapDrawable有原始宽度,而ShapeDrawable没有
     protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
    
      protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }
复制代码

View的测量过程总结一下:

根据widthMeasureSpec和heightMeasureSpec

LinearLayout#onMeasure()

前面我们说了View#measure()是final类型,不能被重写,需要通过重写onMeasure()来实现自己的尺寸测量

但是ViewGroup中既没有measure()方法,也没有onMeasure()方法)

因为不同的ViewGroup(LinearLayout、relativeLayout、constrainstLayout、frameLayout)有不同的布局特性,,测量自己大小的方式也不一样,比如说LinearLayout会根据orientation,在垂直方向或者水平方向进行叠加;FrameLayout会取子View里最大的一个作为自己的大小;所以,ViewGroup的子类需要各自实现onMeasure()方法。

这里我们以LinearLayout为例: 先看个图,省略部分细节

LinearLayout#onMeasure()

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
复制代码

LinearLayout#measureVertical()

    1. 遍历所有子View & 测量:measureChildren()
    1. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值(自身实现)
    1. 存储测量后View宽/高的值:调用setMeasuredDimension()
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {

        final int count = getVirtualChildCount(); // childCount
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight; //如果有weight属性,需要进行二次测量
            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
                //测量子View并给子View的widthMeasureSpec/heightMeasureSpec赋值
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight); 

                final int childHeight = child.getMeasuredHeight();
                final int totalLength = mTotalLength; //h用于存储LinearLayout在竖直方向的高度
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child)); //每次测量完child会进行叠加
        }
        setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        (childState<复制代码

LinearLayout#measureChildBeforeLayout()

void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }
复制代码

ViewGroup#measureChildWithMargins()

  • 根据父View的measureSpec和子View的layoutParams,得到子View的measureSpec,但是这不是子View最终的measureSpec(前面我们分析的时候还有getDefaultSize())

  • child.measure(childWidthMeasureSpec, childHeightMeasureSpec)

      View#measure(widthMeasureSpec,  heightMeasureSpec) 
      View.onMeasure(widthMeasureSpec,  heightMeasureSpec)
      setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                              getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec))
    复制代码
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
复制代码

ViewGroup#getChildMeasureSpec()

根据父View的measureSpec和子View的layoutParams,得到子View的measureSpec

  • 如果子view的layout_width是固定的dp/px,则子View的MeasureSpec = exactly + childSize

  • 如果子view的layout_width是match_parent

      如果父View是exactly,则子View与父View等大,子View的MeasureSpec = exactly + parentSize
      如果父View是at_most,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize
      如果父View是unspecified,则子View想要多大可以有多大,size已经无意义,子View的MeasureSpec = unspecified + 0 
    复制代码
  • 如果子View的layout_width是wrap_content

      如果父View是exactly,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize
      如果父View是at_most,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize
      如果父View是unspecified,则子View想要多大可以有多大,size已经无意义,子View的MeasureSpec = unspecified + 0 
    复制代码

针对MeasuerSpec = unspecified + 0的状况,会通过child.measure -> getDefaultSize() -> getSuggestedMinumSize()来设定。

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
//parentSize是 Math.max(0, specSize - padding) ,padding是所有已用空间+padding+margin
        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 not 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 not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
复制代码

view#layout()

  • View的最终的布局位置和大小完全由mLeft、mTop、mRight、mBottom这4个参数决定,确定了左上角的位置和宽高也就知道四个点的位置。

      (mLeft,mTop, mRight, mBottom)
      (mLeft,mTop, mLeft + getMeasuredWidth(), mTop + getMeasuredHeight())
    复制代码
  • 也就是说在layout过程中,通过getMeasuredWidth()、getMeasuredHeight得到view的宽度、高度

      getMeasuredWidth() = mMeasuredWidth & MEASURED_SIZE_MASK // mMeasuredWidth的低30位
      getMeasuredHeight() = mMeasuredHeight & MEASURED_SIZE_MASK // mMeasuredHeight的低30位
    复制代码
  • layout之后可以通过getWidth()、getHeight()得到view宽度、高度

      getWidth() = mRight - mLeft
      getHeight() = mBottom - mTop
    复制代码
  • 一般来说getMeasuredWidth() == getWidth(),也可以不一样。

  public void layout(int l, int t, int r, int b) {
  
  //存储旧值,用于onLayoutChangeListener
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
             
        }
    }
复制代码

View#setFrame()

  protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            //用于onSizeChanged的监听
            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left; //设置一批新的left、top、right、bottom
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }
        }
        return changed;
    }
复制代码

View#onLayout() View没有子View,所以onLayout是个空的实现

 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }
复制代码

ViewGroup#layout()

final方法,子类不能重写,会调用super.super.layout(l, t, r, b)

    @Override
    public final void layout(int l, int t, int r, int b) {
            super.layout(l, t, r, b);
    }
复制代码

ViewGroup#onLayout()

因为不同的ViewGroup具有不同的布局特性,所以定义为抽象方法,所有继承自ViewGroup的都需要重写onLayout()

@Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);
复制代码

LinearLayout#onLayout()

这里仍然以LinearLayout的orientation为vertical的为例

  @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }
复制代码

LinearLayout#layoutVertical()

  • 遍历子View,对子View进行摆放。

      * 根据android:gravity属性对childTop进行调整
      * 根据android:gravity属性对childLeft进行调整
      * setChildFrame(child, childLeft, childTop, childWidth, childHeight)
    复制代码
void layoutVertical(int left, int top, int right, int bottom) {
        int childTop;
        int childLeft;
        final int count = getVirtualChildCount();
//根据android:gravity属性对childTop进行调整
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
         
                final int childWidth = child.getMeasuredWidth(); //measureSize,所以要先measure再layout
                final int childHeight = child.getMeasuredHeight();
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();
//根据android:gravity属性对childLeft进行调整

                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            }
        }
    }
复制代码

View#getMeasureWidth()

 public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
复制代码

LinearLayout#setChildFrame()

 private void setChildFrame(View child, int left, int top, int width, int height) {
        child.layout(left, top, left + width, top + height);
    }
复制代码

View#draw()

  • 绘制背景
  • 绘制view内容
  • 绘制children
  • 绘制装饰
public void draw(Canvas canvas) {

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        if (!verticalEdges && !horizontalEdges) {
            if (!dirtyOpaque) onDraw(canvas);

            dispatchDraw(canvas);

            onDrawForeground(canvas);

            drawDefaultFocusHighlight(canvas);

    }
复制代码

View#onDraw() 自定义View都要重写onDraw,比如说textView/button/imageView啦

protected void onDraw(Canvas canvas) {
    }
复制代码

ViewGroup#dispatchDraw()

protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
    
        for (int i = 0; i < childrenCount; i++) {

                    more |= drawChild(canvas, transientChild, drawingTime);
    
        }
  
    }
复制代码

ViewGroup#drawChild()

 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }
复制代码

LayoutParams

ViewGroup.LayoutParams

public static class LayoutParams {
        public static final int FILL_PARENT = -1;
        public static final int MATCH_PARENT = -1;
        public static final int WRAP_CONTENT = -2;

        public int width;
        public int height;

        public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }
复制代码

ViewGroup.MarginLayoutParams

public static class MarginLayoutParams extends ViewGroup.LayoutParams {
  
        public int leftMargin;
        public int topMargin;
        public int rightMargin;
        public int bottomMargin;
        private int startMargin = DEFAULT_MARGIN_RELATIVE;
        private int endMargin = DEFAULT_MARGIN_RELATIVE;
    }
复制代码

LinearLayout.LayoutParams

ViewGroup的继承类都有其各自不同的特性,LayoutParams就是它们的属性特征。

public static class LayoutParams extends ViewGroup.MarginLayoutParams {

        public float weight;
        public int gravity = -1;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a =
                    c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);

            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);

            a.recycle();
        }
    }
复制代码

参考:

Android View的绘制流程

转载于:https://juejin.im/post/5b6bd16f5188251ac5552396

你可能感兴趣的:(Android View绘制流程(源码 API27))