view的绘制流程

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
EXACTLYparent_size固定尺寸EXACTLY固定尺寸
EXACTLYparent_sizematch_parentEXACTLYparent_size
EXACTLYparent_sizewrap_contentAT_MOSTparent_size
AT_MOSTparentsize固定尺寸EXACTLY固定尺寸
AT_MOSTparent_sizematch_parentAT_MOSTparent_size
AT_MOSTparent_sizewrap_contentAT_MOSTparent_size
UNSPECIFIEDparentsize固定尺寸EXACTLY固定尺寸
UNSPECIFIEDparent_sizematch_parentUNSPECIFIEDparent_size
UNSPECIFIEDparent_sizewrap_contentUNSPECIFIEDparent_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.java
private 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的子View
dispatchDraw();

...
//步骤五:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left,top,right,top+length,p);
...
canvas.restoreToCount();

..
//步骤六:绘制view的装饰(例如滚动条)
onDrawScrollBars(canvas);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值