View的工作流程

view的工作流程主要分为三个1.measure过程,测量view的宽度高度2.layout过程,确定位置3.draw绘制页面的,View的整个绘制过程从ViewRootImpl的performTraversals()这个是整个View绘制的入口

//伪代码只标注重要的部分
private void performTraversals() {
    //这个是整个measure的入口  实际调用的是view的measure方法
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    //这个layout的入口 实际调用的是view.layout()方法
    performLayout(lp, mWidth, mHeight);
    //整个draw的入口  实际调用的是view的draw()
    performDraw();
}

1.measure过程

(1)MeasureSpec测量规格

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << View.MeasureSpec.MODE_SHIFT) - 1) int size,
                                      @MeasureSpecMode int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
}

首先先明确这个是描述测量结果的一般用一个32位的值高位2位表示测量的mode 模式,低32位表示测量的大小,很多系统源码喜欢用移位和异或等运算表达包含两个数字这种结合,大概是为了节约内存吧

UNSPECIFIED 不限制的模式,父容器不限制当前的大小,当前容器想要多少就是多少,这个一般用于系统内部

EXACTLY 精确模式,父容器已经有明确的大小,这个时候由view的size决定,一般对应view的match_paent和具体大小这两种情况

AT_MOST 最大模式,父容器限制住最大的大小,view最大也就是这个大小,对应wrap_content

这里先简单总结出结论,具体为什么这样,下面的代码会对结论有个详细的描述

(2)view的measure

measure 方法是一个final的方法,子类不能复写,咱们先假定页面只有一个View,先分析单一view的情况,这种比较简单,从简单到复杂,分析一下整个过程

//发现这里面已经生成测量的高宽了,这样说明是从ViewGroup里面生成的高宽,这里先直接得出结论,一会分析ViewGroup的时候就能看见
public final void measure(int widthMeasureSpec, int heightMeasureSpec){
    onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//主要是设置高宽
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//获取建议的宽度 宽度跟这个大致相同
protected int getSuggestedMinimumWidth() {
    //如果没有背景用的是android:minWidth  这个属性设置的宽度,如果有背景取 背景 和android:minWidth的比较大的值
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
/**
 *
 * @param size  getSuggestedMinimumWidth和getSuggestedMinimumHeight的size
 * @param measureSpec 获取的测量的规格,这个是由父容器提供的,ViewGroup里面可以看到
 * @return
 */
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    //根绝mode返回测量的高度 这个里面可以看到
    switch (specMode) {
        //系统内部测量的时候,一般用不到
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
            //这个时候都是测试的size
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

从上面可以看到最终得到的结果都是测量的specSize,当当前的View使用wrap_content的时候,specMode当前的是MeasureSpec.AT_MOST,使用match_parent当前的是MeasureSpec.EXACTLY,这里面可以证明自定义View的时候wrap_content 使用效果跟match_parent 一样了都是一个,所有自定义View的时候要在onMeauser里面对wrap_content的时候进行处理,设置默认的宽高,具体的代码在下面的ViewGroup看一下

(3)viewGroup的measure

如果页面显示有ViewGroup包含几个子View这种情况的时候,测量的时候也会先调用measure 这是由View实现的fianal的方法,也会调用onMeasure方法但是ViewGroup没有onMeasure的方法,网上都是直接从measureChildren方法开始的,我之前一直查找都没有调用这个方法,不知道为什么会从这里面开始,后来看自定义ViewGroup的时候才发现,要重写onMeasure()方法,这个时候会调用measureChildren()测量宽高,因为ViewGroup是个抽象的view集合,只有具体的如LinearLayout才会复写onMeasure()这个时候才有意义,以为ViewGroup是个抽象的,具体测量流程没有,所以会在实现的如LinearLayout类可以看到onMeasure()方法,然后getChildMeasureSpec()生成子View的测量规格。

//循环遍历子View的宽高
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
//测量单个的View的高度和宽度
protected void measureChild(View child, int parentWidthMeasureSpec,
                            int parentHeightMeasureSpec) {
    final ViewGroup.LayoutParams lp = child.getLayoutParams();
    //从这个方法可以看出子view的宽度跟 父view的宽度的规格和 当前view的宽度的LayoutParam决定
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
​
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
​
/**
 *
 * @param spec 父view的测量的规格
 * @param padding 间距
 * @param childDimension 子view的宽度
 * @return
 */
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;
    //父view的mode 
    switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == ViewGroup.LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == ViewGroup.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 == ViewGroup.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 == ViewGroup.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 == ViewGroup.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 == ViewGroup.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);
}

从上面的一大片代码可以看到View的测量的高宽是有父view的MeasureSpec 和子view的LayoutParams共同决定的可以总结出来普通自定义view的测量的高宽了

总结:子view的size 和mode 是由父view的MeasureSpec和当前view的LayoutParams共同决定,在父view的时候已经生成,传递给子view,子View的onMeasure只不过是设置当前的高度宽度

(4)LinearLayout的measure

//根据方向分别进行测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}
//核心代码
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
​measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final float childWeight = lp.weight;
        if (childWeight > 0) {
​
            final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                    Math.max(0, childHeight), MeasureSpec.EXACTLY);
            final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
                    lp.width);
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
​
            //总的高度 是每一个view的测量高度 +margin这些进行叠加
            mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
            childState = combineMeasuredStates(childState, child.getMeasuredState()
                    & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
                    
        }
​
   mTotalLength += mPaddingTop + mPaddingBottom;
​
        int heightSize = mTotalLength;
​
        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
​
        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        //设置宽高,从这里面可以看到都是先遍历完子view,然后设置ViewGroup的高度
         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
    }
}

LinearLayout里面的子View的高宽也是由父view的MeasureSpec和当前view的LayoutParams共同决定,LinearLayout的测量过程就是循环遍历所有的子View,给他一个MeasureSpec 调用view的measure过程,然后调用子View的onMeasure 设置子View的高度,然后view如果还有子View,这就是个循环遍历的过程,当所有子View测量完,设置万高宽之后然后调用自己的setMeasuredDimension 方法设定宽高,这个也是由所有的控件的高度共同决定的,测量过程是递归调用的,父布局在通过自身的MeasureSpec和子view的LayoutParams,生成默认的子View默认的MeasureSpec(getChildMeasureSpec通过这个方法),然后子view在measure一下调用onMeasure方法调用setMeasuredDimension方法,根据默认的MeasureSpec生成最终的测量的高宽,然后父view的根据子view所有的宽度叠加出来最新的宽度,然后设定自己的宽度高度,从这看出来递归调用,最后一层子View确定之后,上层View测量的高宽才能确定。

2layout过程

//确定view的的位置
public void layout(int l, int t, int r, int b) {
​
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
​
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    //确定子View 的位置
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
    }
​
​
}
​
//设置左右上下的位置
protected boolean setFrame(int left, int top, int right, int bottom) {
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
}
onLayout 计算view的位置,view 没有子view所有为空方法所以看一下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);
    }
}
​
//从下面的代码可以看出LinearLayout 子view的位置在他之前子view 叠加出来的高度 +childHeight 这种方式确定左上右下的位置
//这样可以总结出一句话那就是测量的高宽一般都是最后的高宽  getWidth()和getHeight方法都是用的layout左右上下位置相减
//得到的,更这里设置的childWidth和childHeight一致
void layoutVertical(int left, int top, int right, int bottom) {
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
​
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();
​
            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;
​
                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;
​
                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }
​
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }
​
            childTop += lp.topMargin;
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
​
            i += getChildrenSkipCount(child, i);
        }
    }
    
  
}
​
public final int getWidth() {
    return mRight - mLeft;
}
public final int getHeight() {
        return mBottom - mTop;
    }
//调用子view的layout 设置自己的位置 
private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}

总结:layout过程比较简单总结而言就是ViewGroup 调用layout 确定自己的位置,然后调用onLayout生成子View的位置,然后递归调用子View的layout设定位置,层层递归到最后,因为设置的right和bottom 是left+MeasuredWidth和top+MeasuredHeight 而view的getWidth和getHeight 正好是mRight - mLeft和mBottom - mTop 这样计算出来就是MeasuredWidth和MeasuredHeight,所以测量的值就是最终的width 和height 除非layout 设置的时候故意改变其他的值,导致不一致如动态加上100像素这种写法

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

3draw过程

绘制自己的过程

public void draw(Canvas canvas) {
    //画自己的背景
    drawBackground(canvas);
    //绘制自己的content
    onDraw(canvas);
    // 绘制自己的子view
    dispatchDraw(canvas);
    // 绘制装饰 scrollbars这些
    onDrawForeground(canvas);
​
}

绘制过程也是递归调用,先绘制自己的背景内容,递归绘制子View的背景内容

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值