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()这个时候才有意义
//循环遍历子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); 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 方法设定宽高,这个也是由所有的控件的高度共同决定的
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的背景内容
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/addevelopment/893995.html