view的绘制流程

来源:互联网 发布:邓肯为什么不笑 知乎 编辑:程序博客网 时间:2024/06/05 15:27

view层级

Measure

当我们启动一个应用时,会启动一个主Activity,Android会根据我们的Activity布局进行绘制,绘制从ViewRootImpl类中看到performTraversals方法开始

private void performTraversals(){    int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width);    int childHeightMeasureSpec = getRootMeasureSpec(mHeight,lp.height);....    //执行测量流程    performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);    ...    //执行布局流程    performLayout(childWidthMeasureSpec,childHeightMeasureSpec);    ...    //执行绘制流程    performDraw(childWidthMeasureSpec,childHeightMeasureSpec);}

执行流程
自定义view我们都知道重写onMeasure,onLayout,onDraw,但我们总是在写onMeasure 时分类讨论,其实都是一些固定的格式,但身为程序员,我们总是想要知道为什么这么写。
首先我们先介绍一下MeasureSpec,它表示的是一个32位的整形值,最高两位表示测量的模式SpecMode,低三十位表示某种测量模式下的规格大小SpecSize.

public static class MeasureSpec {        private static final int MODE_SHIFT = 30;        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;        /** @hide */        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})        @Retention(RetentionPolicy.SOURCE)        public @interface MeasureSpecMode {}        /**         * Measure specification mode: The parent has not imposed any constraint         * on the child. It can be whatever size it wants.         */        public static final int UNSPECIFIED = 0 << MODE_SHIFT;        /**         * Measure specification mode: The parent has determined an exact size         * for the child. The child is going to be given those bounds regardless         * of how big it wants to be.         */        public static final int EXACTLY     = 1 << MODE_SHIFT;        /**         * Measure specification mode: The child can be as large as it wants up         * to the specified size.         */        public static final int AT_MOST     = 2 << MODE_SHIFT;        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);            }        }        .........

MeasureSpec是View的一个静态内部类。
我们需要重点关注代码中的以下三种测量模式:

  • UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
  • EXACTLY:精确测量模式,当视图的layout_width或者layout_height指定为具体数值,或者match_parent时生效,表示父视图已经决定了子视图的精确大小,这种情况下View的测量值就是Specsize的值。
  • AT_MOST:最大值模式,当该视图的layout_width或者layout_height指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸

对于DecorView而言,他的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;对于普通的View,他的MeasureSpec由父类的MeasureSpec和其自身的LayoutParams共同决定,我们自定义view在重写onMeasure方法时传入的两个参数就是由父类的MeasureSpec和我们自定义view的LayoutParams合成的。
下面我们可以看ViewGroup的一段代码,看完我们就能理解onMeasure的两个参数如何使用了。

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {        final int size = mChildrenCount;        final View[] children = mChildren;        //循环遍历测量子view的大小        for (int i = 0; i < size; ++i) {            final View child = children[i];            //当view的状态为GONE时不执行测量            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {                measureChild(child, widthMeasureSpec, heightMeasureSpec);            }        }    }
 protected void measureChild(View child, int parentWidthMeasureSpec,            int parentHeightMeasureSpec) {        final LayoutParams lp = child.getLayoutParams();        //下面就是根据父类的MeasureSpec组成子view的MeasureSpec        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom, lp.height);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

下面我们就看看getChildMeasureSpec是如何组成子类的MeasureSpec

 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);    }
父类MeasSpec 父类MeasureSpec中的size 子类LayoutParams 子类MeasureSpec 子类MeasureSpec中的size EXACTLY parent_size 固定尺寸 EXACTLY 固定尺寸 EXACTLY parent_size match_parent EXACTLY parent_size EXACTLY parent_size wrap_content AT_MOST parent_size AT_MOST parentsize 固定尺寸 EXACTLY 固定尺寸 AT_MOST parent_size match_parent AT_MOST parent_size AT_MOST parent_size wrap_content AT_MOST parent_size UNSPECIFIED parentsize 固定尺寸 EXACTLY 固定尺寸 UNSPECIFIED parent_size match_parent UNSPECIFIED parent_size UNSPECIFIED parent_size wrap_content UNSPECIFIED parent_size

从上表可以得出当我们在重写子view的onMeasure时,当传过来的Mode为EXACTLY时,此时传过来的size就是我们想要的size,其他两种情况我们需要自己手动测量大小,此时传过来的size并不是我们想要的大小
而我们在写onMeasure时需要保存测量的值

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

我们可以在重写的onMeasure方法中调用setMeasuredDimension方法,传入我们想要设置的size或者直接执行super.onMeasure传入我们设置的size大小
view树

Layout

Layout过程用来确定View在父容器的布局位置,它是由父容器获取子view的位置参数后,调用View的layout方法并将位置参数传入实现。ViewRootImpl的performLayout代码如下:

//ViewRootImpl.java private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,            int desiredWindowHeight) {            ...             host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());            ...     }
//View.java public void layout(int l, int t, int r, int b) {     ...     onLayout(changed,l,t,r,b);     ...
//View.java//这是个空方法,子类如果是ViewGroup类型,则重写这个方法,实现子View的布局流程protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    }

Draw

Draw操作用来将控件绘制出来,绘制的流程从performDraw方法开始

//ViewRootImpl.javaprivate void performDraw() {    ...    draw(fullRedrawNeeded);    ...}private void draw(boolean fullRedrawNeeded) {........if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {                    return;                }                .....} private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,            boolean scalingRequired, Rect dirty) {            ...            mView.draw(canvas);            ... }

可以看到最终调用到每个View的draw方法绘制每个具体的View,绘制基本可以分为六个步骤

//View.java public void draw(Canvas canvas) { ... //步骤一:绘制View背景  drawBackground(canvas);  ...//步骤二:如果需要的话,保存canvas的图层,为fading做准备saveCount = canvas.getSaveCount();....canvas.saveLayer(left,top,right,top+length,null,flags);....//步骤三:绘制View的内容onDraw();...//步骤四:绘制View的子ViewdispatchDraw();...//步骤五:如果需要的话,绘制View的fading边缘并恢复图层canvas.drawRect(left,top,right,top+length,p);...canvas.restoreToCount();..//步骤六:绘制view的装饰(例如滚动条)onDrawScrollBars(canvas);
原创粉丝点击