Android视图绘制流程之measure的使用详情
程序员文章站
2022-04-02 20:41:50
Android视图绘制流程之measure的使用详情
view的measure
private void performMeasure(int childWidthMeas...
Android视图绘制流程之measure的使用详情
view的measure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }performMeasure是入口
直接看measure的onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
getDefaultSize
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; }UNSPECIFIED可以不看。从MeasureSpec中解包出SpecMode、SpecSize。这里的SpecSize就是View测量后的大小。(而View最终的大小是在layout阶段确定的)从代码逻辑中可以看出来,AT_MOST和EXACTLY是一种操作,所以getDefaultSize方法返回的就是SpecSize。
UNSPECIFIED情况一般用于系统内部的测量,所以getSuggestedMinimumHeight()方法获取的是UNSPECIFIED情况下的高,宽亦然。暂时不必深究。
从getDefaultSize方法的实现来看,View的宽高由SpecSize决定,所以可以得出结论:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小。否则在布局中使用wrap_content就相当于使用match_parent。如果View在布局中是wrap_content,那么他的SpecMode是AT_MOST,在这种模式下,他的宽高等于SpecSize。这种情况下,SpecSize是parentSize(父容器中当前可以使用的大小),这种效果和在布局中使用match_parent一致。
如何解决这个问题?
View view = new View(this){ private int mWidth = 20; private int mHeight = 20; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mHeight); } } };其实这个很容易理解。比如TextView,你如果在xml中(即lp中)设置了wrap_content,一般性都是包住文本即可,但是我们不说,谁能知道?所以这个必须得我们去设置。mWidth,mHeight就是我们设置的默认值,在TextView中要进行对字体大小的计算就可以得到大小。如果没有这个设置的话,就相当于match_parent了。
view group的measure
view group是一个抽象类,没有重写view的onMeasure方法,但是提供了一个measureChildren方法。
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); } } }
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }measureChild的过程就是去除子元素的lp,再通过getChildMeasureSpec来创建子元素的MeasureSpec,之后把子元素的MeasureSpec传给child进行测量。
view group没有重写onMeasure,因为onMeasure在不同的布局中测量的细节都不一样。
LinearLayout的onMeasure方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } }看看measureVertical的关键代码
// See how tall everyone is. Also remember max width. for (int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); //... // Determine how big this child would like to be. If this or // previous children have given a weight, then we allow it to // use all available space (and we will shrink things later // if needed). final int usedHeight = totalWeight == 0 ? mTotalLength : 0; measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight); 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)); if (useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); } }会对每个孩子都执行measureChildBeforeLayout方法。这个方法内部会调用子元素的measure方法,这样各个子元素就依次进入measure过程,并且系统会通过mTotalLength这个变量来存储LinearLayout在竖直方向的初步高度。每测量一个子元素,mTotalLength就会增加。增加的部分主要包括了子元素的高度以及子元素在竖直方向上的margin等。当子元素测量完毕后,LinearLayout会测量自己的大小。
// Add in our padding 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; ... setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);当子元素测量完后,LinearLayout会根据子元素的情况来测量自己的大小。对于竖直的LinearLayout来说,他的水平方向的测量过程遵循View的测量过程,竖直方向上的测量过程就和View的不一样了。如果它的布局中高度采用的是match_parent或者具体数值,那么它的测量过程和View一致,即高度为SpecSize。如果它的布局高度采用的是wrap_content,那么它的高度是所有子元素所占用的高度总和,但是仍然不能超过它的父容器的剩余空间。它的最终高度还需要考虑其在竖直方向的padding。这个过程源码如下
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }