从源码角度分析View的绘制流程

 

上一篇文章引出了performTraversals,在内部分别调用了performMeasure,performLayout,performDraw三个方法。这三个方法分别有调用了mView.measure,mView.layout,mView.draw(canvas)三个对应的方法,因此我们找打View中对应的方法。

一、onMeasure

如下measure方法,省略了其他的代码:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

可以看出measure方法是被final修饰,是不可重写了,但是onMeasure方法却是public的,里面传递了widthMeasureSpec,heightMeasureSpec两个参数,这两个参数来自于父容器,所有宽高的测量也受父容器的影响。下面找到父容器ViewGroup看父容器和子View的layoutparams如何影响View的测量。

1、MeasureSpect之三种模式

MeasureSpect由于int是32位的,用高两位表示mode,低30位表示size。共有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。

  • MeasureSpec.EXACTLY:精确模式
  • MeasureSpec.AT_MOST:至多模式,有多大给多大,但不能超过父容器,相当于wrap_content
  • MeasureSpec.UNSPECIFIED:未指定尺寸模式,相当于match_parent

2、三种模式如何受父容器影响

子View的MeasureSpec由父View的MeasureSpec和子View本身的LayoutPramas共同决定,在ViewGroup的getChildMeasureSpec方法中实现:

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) {
        //(1)父容器精确模式
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                //子View设置精确模式
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                //子View设置精确模式
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                 //子View设置至多模式
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //(2)父容器至多模式
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                resultSize = childDimension;
                //子View设置精确模式
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                 //子View设置至多模式
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                 //子View设置至多模式
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //(3)父容器不确定模式
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                //子View设置精确模式
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                //子View设置不确定模式
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                //子View设置不确定模式
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

总结:

(1) 父类MeasureSpec.EXACTLY,子类:

a.具体尺寸,或者LayoutParams == MATCH_PARENT,则为EXACTLY

b.LayoutParams == WRAP_CONTENT,则为AT_MOST

(2) 父类MeasureSpec.AT_MOST,子类:

a.具体尺寸,则为EXACTLY

b.LayoutParams == WRAP_CONTENT或者LayoutParams == MATCH_PARENT,则为AT_MOST

(3)父类MeasureSpec.UNSPECIFIED,子类:

a.具体尺寸,则为EXACTLY

b.LayoutParams == WRAP_CONTENT或者LayoutParams == MATCH_PARENT,则为UNSPECIFIED

从上面的总结上可以看出来,当字View尺寸固定时,不管父容器是什么模式,子类都是精确模式。

在measure方法中,有一个非常重要的方法setMeasuredDimension,当尺寸宽高变化的时候都需要调用这个方法。总结measure流程如下:

View: performTraversals ——> performMeasure ——> mView.measure ——> onMeasure ——> setMeasuredDimension

ViewGroup: ViewGroup.measureChild —— 遍历子View——> View.onMeasure ——> setMeasuredDimension

二、onLayout

View中:空实现:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

    }

ViewGroup中是抽象的:

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

以RelativeLayout为例,看看实现:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        //遍历View
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                RelativeLayout.LayoutParams st =
                        (RelativeLayout.LayoutParams) child.getLayoutParams();
                //设置子View的测量
                child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
            }
        }
    }

三、onDraw

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. 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. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

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

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
  1. 绘制背景
  2. 需要的话,保存画布
  3. 绘制内容,空实现onDraw,一般需要重写
  4. 绘制子View,View空实现dispatchDraw,一般需要重写
  5. 如果需要的话绘制锯齿边缘
  6. 绘制滚动条

wiew空实现dispatchDraw,ViewGroup需要实现,内部调用:ViewGroup ——> drawChild ——> child.draw,最后还是调用View的Draw。

四、自定义控件继承自View

需要重写measure方法进行测量,设梁实际尺寸之后需要设置setMeasuredDimension,宽高的测量可你仿照源码:

	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;
	 }

作为setMeasuredDimension的参数进行传递。view的自定义比较简单,这里不做单独分析。详情可以参考之前的文章:

自定义EditText:https://blog.csdn.net/yoonerloop/article/details/61616065

自定义指示器指示器Indictor:https://blog.csdn.net/yoonerloop/article/details/51940612

四、自定义控件继承自ViewGroup

在onMeasure方法中需要测量子控件大小,还需要测量自身大小,然后调用setMeasuredDimension设置自身的宽高,在测量子view是可用使用ViewGrou提供的测量子view的方法,也可以自定义测量规则,最后在onLayout方法中对子view进行布局。

1、计算child的测量规则

getChildMeasureSpec

2、将计算出来的测量规则传递给子View进行测量

child.measure

3、测量完成后获取子View的尺寸

child.getChildMeasureSize

4、VIewGroup根据自身Padding和子View的尺寸设置尺寸

setSize

5、尺寸变化后调用setMeasuredDimension

setMeasuredDimension(width, height);

下一篇文章将自定义一个简单的ViewGroup进行实战,实现FlowLayout效果。




 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值