首页 > 代码库 > 流式布局的实现-2-onMeasure的实现
流式布局的实现-2-onMeasure的实现
在这一篇中,我们主要是来实现FlowLayout中的onMeasure函数。
先说一说onMeasure,可以说重载 onMeasure(),onLayout(),onDraw()三个函数构建了自定义View的外观形象。再加上onTouchEvent()等重载视图的行为,可以构建任何我们需要的可感知到的自定义View。我们知道,不管是自定义View还是系统提供的TextView这些,它们都必须放置在 LinearLayout等一些ViewGroup中,因此理论上我们可以很好的理解onMeasure(),onLayout(),onDraw()这 三个函数:1.View本身大小多少,这由onMeasure()决定;2.View在ViewGroup中的位置如何,这由onLayout()决 定;3.绘制View,onDraw()定义了如何绘制这个View。
在TextView中onMeasure是:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; ... if (widthMode == MeasureSpec.EXACTLY) { // Parent has told us how big to be. So be it. width = widthSize; } else { if (mLayout != null && mEllipsize == null) { des = desired(mLayout); } ... } setMeasuredDimension(width, height); }
首先我们要理解的是widthMeasureSpec, heightMeasureSpec这两个参数是从哪里来的?onMeasure()函数由包含这个View的具体的ViewGroup调用,因此值也是 从这个ViewGroup中传入的。这里我直接给出答案:子类View的这两个参数,由ViewGroup中的 layout_width,layout_height和padding以及View自身的layout_margin共同决定。权值weight也是尤其需要考虑的因素,有它的存在情况可能会稍微复杂点。
了解了这两个参数的来源,还要知道这两个值的作用。我们只取 heightMeasureSpec作说明。这个值由高32位和低16位组成,高32位保存的值叫specMode,可以通过如代码中所示的 MeasureSpec.getMode()获取;低16位为specSize,同样可以由MeasureSpec.getSize()获取。那么 specMode和specSize的作用有是什么呢?要想知道这一点,我们需要知道代码中的最后一行,所有的View的onMeasure()的最后一行都会调用setMeasureDimension()函数的作用——这个函数调用中传进去的值是View最终的视图大小。也就是说 onMeasure()中之前所作的所有工作都是为了最后这一句话服务的。
之后我们开始一片一片分析代码:
第一部分:int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
分别获得在widthMeasureSpec,heightMeasureSpec中的size与mode:
getSize,getMode和过程中涉及到的变量:
public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } 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;第二部分:
int width = 0; int height = 0; // 记录每一行的宽度与高度 int lineWidth = 0; int lineHeight = 0; // 得到内部元素的个数 int cCount = getChildCount();在注释中都已说明,就不解释了;
第三部分:
for (int i = 0; i < cCount; i++) { View child = getChildAt(i); // 测量子View的宽和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); // 得到LayoutParams MarginLayoutParams lp = (MarginLayoutParams) child .getLayoutParams(); // 子View占据的宽度 int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; // 子View占据的高度 int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; // 换行 if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) { // 对比得到最大的宽度 width = Math.max(width, lineWidth); // 重置lineWidth lineWidth = childWidth; // 记录行高 height += lineHeight; lineHeight = childHeight; } else // 未换行 { // 叠加行宽 lineWidth += childWidth; // 得到当前行最大的高度 lineHeight = Math.max(lineHeight, childHeight); } // 最后一个控件 if (i == cCount - 1) { width = Math.max(lineWidth, width); height += lineHeight; } }
先获得子View,再获得子View的layoutParams,因为是有间距的,所以注意要子View的LayoutParams强制转化为MarginLayoutParams;
重新计算子View的宽:因为有左右间距,所以新的宽是原宽加上左右间距;同理,高也是如此;
之后判断要不要换行,即已计算的行宽加上新的子View的宽,如果大于FlowLayout的宽减去左右边距说明剩余的宽度,不够加上新的子View,要把新的子View放到新的一行中去(其实有做过acm的话,可以用到线段树来进行优化),否则加入到原行中。之后在这两种情况下对行高,行宽,高和宽做不同的调整;
如果到了最后一个子View的话:如果要换行的话,高只是记录到之前的部分,而对换行后新加的高度即最后一个子View的高没有加上,宽的话,如果最后一个子View的宽很大超过之前的话,也没有更新;如果不要换行的话,最后一行的高与宽就始终没有加上;所以最后一个View要进行特判;因为无论是换行还是不换行,行宽都更新过了,所以只要和最大的宽做比较即可;高则只要加上行高即可;
第四部分:setMeasuredDimension( // modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(), modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop()+ getPaddingBottom()// );判断是不是MeasureSpec.EXACTLY即是不是内容铺满父容器,是的话,就直接将一开始调用getSize得到的宽高传入即可,如果不是,则要将测得的宽高加上边距传入到setMeasuredDimension函数中,来确定View最终的视图大小。
流式布局的实现-2-onMeasure的实现