绘制机制

绘制流程

  • ViewRootImpl.performTraversals 起点
  • Measure 决定view大小
  • Layout 决定view的位置
  • Draw 绘制

performTraversals

依次执行measure,layout,draw

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);
        }
    }
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ...
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...
    }
private void performDraw() {
        ...
    }
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        ...
    }

measure

父容器传递measureSpec至子容器。子容器根据measureSpec,并结合自身的layoutParams,测量出大小。父容器最后根据子容器的测量值,再测量出自身规格.

  • 元素visibility为GONE的不参与measure
  • 当viewGroup的子类的属性有margin或padding时,子类会执行viewGroup中的measureChildWithMargins,最终测量的大小会specSize - padding。
  • View中的源码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
//获取的是android:minHeight属性的值或者View背景图片的大小
protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }
//获取默认的大小,size为自身大小,measureSpec为父元素传下来的,可见一个view的大小是由父元素的measureSpec和view的layoutParams共通决定
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的子类有各自的实现方式,不是直接拿measurespec和layoutParams进行测量
  • ViewGroup中没有实现onMeasure,都由ViewGroup中的子类进行实现,如FrameLayout等,ViewGroup的子类在onMeasure过程中,递归所有子view,执行measureChildWithMargins、measure、onMeasure。测量完所有子View后,再通过用view中的setMeasuredDimension进行测量。

measureChildWithMargins

  • 如果view有固定值,则view的measureSpec的mode为EXACTLY,size为该固定值。
  • 在父元素为EXACTLY的情况下。子元素若为match_parent,则子元素的measureSpec,mode则为EXACTLY,size为父元素size。子元素若为wrap_content,则子元素的measureSpec,mode为AT_MOST,size为Math.max(0, specSize - padding)
  • 在父元素为AT_MOST的情况下。子元素无论layoutParams是match_parent还是wrap_content,子元素的measureSpec,mode都为AT_MOST,size都为Math.max(0, specSize - padding)
  • 在父元素为UNSPECIFIED时,子元素无论是match还是wrap,子元素的measureSpec,mode为UNSPECIFIED,size为View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
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);
    }
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:
            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;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

layout

  • 最先执行的是viewGroup中的layout,判断当前有没有正在执行的layoutTransition,没有就调用view中的layout,否则,就等transition执行完后,继续调用。
  • view的layout中,有两种设置view位置的方法,一种是setOpticalFrame,另外一种是setFrame。调用哪个,取决于父对象是不是viewGroup,且是否可见,若全是,则调用setOpticalFrame,getOpticalInsets则是获取drawable的insets。
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
        Insets parentInsets = mParent instanceof View ?
                ((View) mParent).getOpticalInsets() : Insets.NONE;
        Insets childInsets = getOpticalInsets();
        return setFrame(
                left   + parentInsets.left - childInsets.left,
                top    + parentInsets.top  - childInsets.top,
                right  + parentInsets.left + childInsets.right,
                bottom + parentInsets.top  + childInsets.bottom);
    }
  • setFrame结束后,还会执行OnLayoutChangeListener.onLayoutChange函数,若需要监听,layout的变化,可addOnLayoutChangeListener。
  • layout的四个参数,默认是建立在measure的width和height基础上的,当也可以重写。并不依赖measure。如果重写ltrb,则getMeasuredWidth和getWidth取得的值不一致。

draw

ViewGroup中并没有重写draw,则执行view中draw

  1. 如果有设置背景,则绘制背景 Draw the background
  2. 保存canvas层 If necessary, save the canvas' layers to prepare for fading
  3. 绘制自身内容 Draw view's content
  4. 如果有子元素则绘制子元素 Draw children
  5. 绘制效果 If necessary, draw the fading edges and restore layers
  6. 绘制装饰品(scrollbars) Draw decorations (scrollbars for instance)
  • canvas对象由ViewRootImpl的drawSoftware建立,传递至view中。
  • 每当调用invalite时,都会将之前view区域设为dirty
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        ...
        canvas = mSurface.lockCanvas(dirty);
        ...
    }
public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mLockedObject != 0) {
                // Ideally, nativeLockCanvas() would throw in this situation and prevent the
                // double-lock, but that won't happen if mNativeObject was updated.  We can't
                // abandon the old mLockedObject because it might still be in use, so instead
                // we just refuse to re-lock the Surface.
                throw new IllegalArgumentException("Surface was already locked");
            }
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
    }

surfaceView

measureSpec

  • 一个measureSpec包含了父容器传递给子容器的布局要求。
  • 包含了mode与size的组合int值,高两位为mode,后面是size。
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);
            }
        }
  • 三种模式
    • UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大
    • EXACTLY: 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。
    • AT_MOST:子容器可以是声明大小内的任意大小
posted @ 2017-02-06 16:24  沁河  阅读(160)  评论(0编辑  收藏  举报