android开发分享View的工作流程

view的工作流程主要分为三个1.measure过程,测量view的宽度高度2.layout过程,确定位置3.draw绘制页面的,View的整个绘制过程从ViewRootImpl的performTraversals()这个是整个View绘制的入口//伪代码只标注重要的部分private void performTraversals() { //这个是整个measure的入口 实际调用的是view的measure方法 performMeasure(childWidthMeasureS..

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的工作流程

总结:子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

(0)
上一篇 2021年10月21日
下一篇 2021年10月21日

精彩推荐