View体系学习之绘制流程

1. onMeasure过程

1.1 View

首先介绍的是View的Measeure过程.从measure方法说起,他是View内部的一个方法,并且用final修饰.他是View对象Measure过程的起始点:

public final void measure(int widthMeasureSpec, int heightMeasureSpec){
    ...
    // 如果强制执行layout或者需要layout的情况
    if (forceLayout || needsLayout) {
        ...
        // 如果缓存里很早不到或者被要求忽略缓存中心layout
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
        ...
    }
    ...
}

在必要的条件下会调用onMeasure方法,这个在自定义View的时候是可以重写的.当然View有个默认的实现.

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

这里面调用了setMeasuredDimension方法.

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ...
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

最后是调用了方法.将measuredWidth以及measuredHeight赋值给了对应的全局变量.就是说这边确定了布局的长和宽.那下面看看数值怎么确定的,调用的是getDefaultSize方法:

//这边mMinWidth是xml文件里面配置的最新宽度,而mBackground.getMinimumWidth()是背景图的宽度
//也就是说getSuggestedMinimumWidth获取的是背景和配置值中的最大值
protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

//这边的size也就是上面getSuggestedMinimumWidth得出的最大值,而measureSpec是包含mode以及size信息的数据
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;
    }

这里的measureSpec数据包含了size和mode.通过getMode以及getSize方法获取.

首先specMode取决于配置文件中的wrap,match以及固定数值.其中wrap表示的是AT_MOST,也就是他的最大值不超过父类.而match以及固定数值对应的是EXACTLY,就是说这个值是已经确定的.

在默认View中AT_MOST和EXACTLY是一致的.之所以在textview等控件中不一样,因为被重写了,这也是自定义View所要做的动作.

这里是将经过一系列计算得出的测量值赋值给result.

而UNSPECIFIED的情况比较特殊,他直接取决于View的背景或者自定义的长度.

比如说RcycleView就是强制子类使用UNSPECIFIED模式来,因为recycleView的子类长宽并不固定.

如果子类只有一个View,而且无背景且无设置mMinWidth,那么它的长度将是0.

而如果子类是FrameLayout,那么它的长度是正常的,因为FrameLayout重写了UNSPECIFIED模式.

1.2 ViewGroup

下面介绍下ViewGroup的measure流程:

首先它的measure方法也是用的View的默认方法.因为ViewGroup继承自View,而View的measure方法是不可重写的.

那么下面看下onMeausure,由于不同的ViewGroup差异较大,所以ViewGroup并没有写默认的onMeausure方法.

下面以LinearLayout作为例子分析一下:

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

分为横向布局和纵向布局,这个比较熟悉.跟进去看下

void measureVertical(int widthMeasureSpec, int heightMeasureSpec){
	...
    //获取竖直方向的View个数
    final int count = getVirtualChildCount();
    
    for (int i = 0; i < count; ++i){
        
        //如果是GONE,则直接跳过
        if (child.getVisibility() == View.GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
            }
        
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                // 这些View后续会再进行绘制
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                
                //该方法内部最终会调用measureChildren(),从而 遍历所有子View
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                //获取子View的高度
                final int childHeight = child.getMeasuredHeight();
                
                //合并所有子类的长度
                if (useExcessSpace) {
                    // Restore the original height and record how much space
                    // we've allocated to excess-only children so that we can
                    // match the behavior of EXACTLY measurement.
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

                ...
            }
    }
    
    ...
        
        //计算出减去确定宽度后的剩余的长度
        int remainingExcess = heightSize - mTotalLength
                + (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
    	//上面如果发现有设置weigth,skippedMeasure == true.那么这边就会重绘
        if (skippedMeasure
                || ((sRemeasureWeightedChildren || remainingExcess != 0) && totalWeight > 0.0f)){
            ...
        }
    
    ...
        
        maxWidth += mPaddingLeft + mPaddingRight;

        // Check against our minimum width
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    	//经过一系列计算,setMeasuredDimension方法设置最终绘制出的结果
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
}

这边主要是会调用measureChildBeforeLayout方法去遍历子类.其他的同样是调用View的方法,比如苏红最后的setMeasuredDimension方法.也就是说ViewGroup与View的差别主要集中在它多了个统筹的作用.这也符合高内聚低耦合的思想.ViewGroup和View分别处理自己的事情.

2. onLayout过程

2.1 View

同样的从layout开始

public void layout(int l, int t, int r, int b){
    ...
        //判断当前View大小和位置是否发生了变化
    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);
        ....
    }
}

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

//setOpticalFrame最终也会调用setFrame.也就是说在onLayout之前就已经确定了,onLayout是一个只是通知而已.
//在View中onLayout是一个空实现
protected boolean setFrame(int left, int top, int right, int bottom){
    mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
    ....
}

2.2 ViewGroup

在ViewGroup中onLayout是一个抽象方法,也就是说强迫继承类实现.

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

下面同样用LinearLayout来分析:

@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);
        }
    }
    
    void layoutVertical(int left, int top, int right, int bottom){
        //获取子类的数量
        final int count = getVirtualChildCount();
        //遍历子类View
    	for (int i = 0; i < count; i++){
            
            //调用子类的layout方法
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
        }
    }

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

可见在View或者ViewGroup中并不会改变本身的数值,因为在onLayout中的时候,位置就已经确定了.onLayout的作用在View中是通知,而在ViewGroup中就是给子View安排位置的作用.

3. onDraw过程

3.1 View

从draw开始看起:

public void draw(Canvas canvas){
	// Step 1, draw the background, if needed
    // 第一步,画背景
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
    	//第二步:画自身.这个就是自定义View的主要战场了
    	if (!dirtyOpaque) onDraw(canvas);
    
            //第三步:画子View
            dispatchDraw(canvas);
    
    		//第四步:画前景
    		onDrawForeground(canvas);
    
    // we're done...
    //基本就这四步骤了...
            return;
}

3.2 ViewGroup

ViewGroup最dispatchDraw实现了该方法.因为Draw发生在控件内部,并不像位置一样,涉及控件之间的关系.

@Override
    protected void dispatchDraw(Canvas canvas){
        final int childrenCount = mChildrenCount;
        
        //遍历子View并绘制
        for (int i = 0; i < childrenCount; i++){
            ...
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {3
                    //绘制子类
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
        }
    }


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

分析完毕!

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值