自定义控件之流式布局
2016-01-03 22:46
381 查看
这段时间偷懒了,全去dota去了。都没有心情敲代码了。写了个流式布局。练习下自定义viewgroup,再准备写个圆形菜单来练习练习。
下面看看效果:
流式布局:
viewgroup中我们必须实现onMeasure(),和onLayout()。两个方法,onMeasure()是测量布局的尺寸的。onLayout()方法是控制子控件位置的。然后执行完了之后还会执行onDraw()。如果你还需要 绘画其他东西,就可以自己画。
如果你自定义的是View类型的控件,如果你不重写onMeasure()方法那么wrap_content属性将不会有任何作用和match_parent一样的效果。
现在我们自定义的是viewgroup ,因为viewgroup是个抽象方法,我们必须实现onMeasure(),onLayout()。
onMeasure()方法中有两个参数int widthMeasureSpec, int heightMeasureSpec,这两个参数将对应的宽高的size,和mode都放进这两个参数中了。
获取方式如下:
这里的heightSize , widthSize 都是返回match_parent状态下的尺寸,或者精确的某个值。
widthMode,heightMode 如果是MeasureSpec.EXACTLY那么对应的是match_parent或者精确的值。
如果返回的是MeasureSpec.AT_MOST那么对应的wrap_content就是自适应。这个就需要我们手动进行计算了,并且还需要参考heightSize ,widthSize ,虽然我们是wrap_content,但是这两个值给我们的是match_parent对应的尺寸。所以我们自已适应的时候需要参考这两个值,不能超过他们,因为这是给我们最大的值了。最后还需要考虑到子控件的margin,和自己的padding属性。
贴出我的onMeasure
在需要子控件的尺寸之前需要测量子控件
measureChild(view, widthMeasureSpec, heightMeasureSpec);
注意:如果要将子控件的LayoutParams 转成 MarginLayoutParams 需要在viewgroup中添加
先判断这一行能否还能继续放下当前控件,因为最大的宽度就是widthSize,不能比他还大了。如果放不下了就移动到下一行去。并这一行的高度其实就是最高的控件的高度
当移动到下一行就将测量的高度加上上一行最高的高度。
并且判断是否是最宽的一行。如果是的话那么就把当前布局的宽度设置成这个宽度。
记住在最后一行的时候需要将自己的行的高度添加上去
然后将自己的padding计算上去。
最后在判断自己的layout_width , layout_height 属性对应的是否是MeasureSpec.EXACTLY,如果是的话就直接使用widthSize,heightSize最大的尺寸了。
最后设置尺寸
然后我们来看看onLayout函数:
这里的基本逻辑和onMeasure中一致。判断这一行是否能放下,能放下就放下(阿弥陀佛)
不能放下就到下一行去。
好了最后贴出源码地址:
源码下载
加个好友共同学习(不是公众号):
因为小弟水平有限,如果有写的有问题,希望指出。
下面看看效果:
流式布局:
一 概述:
流式布局就将其子控件,从左往右进行排列。如果这一行能放下当前的控件(需要考虑margin,和控件的宽度)那么久在当前放下控件,如果放不下控件,就放到第二行去。viewgroup中我们必须实现onMeasure(),和onLayout()。两个方法,onMeasure()是测量布局的尺寸的。onLayout()方法是控制子控件位置的。然后执行完了之后还会执行onDraw()。如果你还需要 绘画其他东西,就可以自己画。
二 源码解读:
好了,我们可以看看onMeasure()方法。如果你自定义的是View类型的控件,如果你不重写onMeasure()方法那么wrap_content属性将不会有任何作用和match_parent一样的效果。
现在我们自定义的是viewgroup ,因为viewgroup是个抽象方法,我们必须实现onMeasure(),onLayout()。
onMeasure()方法中有两个参数int widthMeasureSpec, int heightMeasureSpec,这两个参数将对应的宽高的size,和mode都放进这两个参数中了。
获取方式如下:
[code] int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec);
这里的heightSize , widthSize 都是返回match_parent状态下的尺寸,或者精确的某个值。
widthMode,heightMode 如果是MeasureSpec.EXACTLY那么对应的是match_parent或者精确的值。
如果返回的是MeasureSpec.AT_MOST那么对应的wrap_content就是自适应。这个就需要我们手动进行计算了,并且还需要参考heightSize ,widthSize ,虽然我们是wrap_content,但是这两个值给我们的是match_parent对应的尺寸。所以我们自已适应的时候需要参考这两个值,不能超过他们,因为这是给我们最大的值了。最后还需要考虑到子控件的margin,和自己的padding属性。
贴出我的onMeasure
[code] protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Log.e("xhc","onMeasure"); super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) { setMeasuredDimension(widthSize, heightSize); return; } //最后管padding int childCount = getChildCount(); int measureWidth = 0, measureHeight = 0; //一行中最高的一个控件 int heightMax = 0, widthMax = 0; for (int i = 0; i < childCount; ++i) { View view = getChildAt(i); MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams(); measureChild(view, widthMeasureSpec, heightMeasureSpec); if ((measureWidth + params.rightMargin + params.leftMargin + view.getMeasuredWidth() + getPaddingRight()) <= widthSize) { //这一行可以放下这个控件 measureWidth += (params.rightMargin + params.leftMargin + view.getMeasuredWidth()); if (heightMax < (view.getMeasuredHeight() + params.topMargin + params.bottomMargin)) { heightMax = (view.getMeasuredHeight() + params.topMargin + params.bottomMargin); } } else { //换行 if ((measureHeight + heightMax) < heightSize) { //换行了 高度增加 measureHeight += heightMax; Log.e("xhc","高度增加"+measureHeight); heightMax = 0 ; } if (widthMax < measureWidth) { //换成最宽的宽度; widthMax = measureWidth; } //行宽重新开始 measureWidth = 0 ; } if(i == (childCount - 1)){ //这个是最后一行并且没到换行的地方需要把这一行的高度加进去 measureHeight += heightMax; } } if (widthMax != 0) { measureWidth = widthMax; } if (measureHeight == 0) { measureHeight = heightMax; } if ((measureWidth + getPaddingLeft()) < widthSize) { measureWidth += getPaddingLeft(); } if ((measureWidth + getPaddingRight()) < widthSize) { measureWidth += getPaddingRight(); } if ((measureHeight + getPaddingTop()) < heightSize) { measureHeight += getPaddingTop(); } if ((measureHeight + getPaddingBottom()) < heightSize) { measureHeight += getPaddingBottom(); } if (widthMode == MeasureSpec.EXACTLY) { measureWidth = widthSize; } if (heightMode == MeasureSpec.EXACTLY) { measureHeight = heightSize; } setMeasuredDimension(measureWidth, measureHeight); }
在需要子控件的尺寸之前需要测量子控件
measureChild(view, widthMeasureSpec, heightMeasureSpec);
注意:如果要将子控件的LayoutParams 转成 MarginLayoutParams 需要在viewgroup中添加
[code] @Override protected LayoutParams generateLayoutParams(LayoutParams p) { return new MarginLayoutParams(p); }
先判断这一行能否还能继续放下当前控件,因为最大的宽度就是widthSize,不能比他还大了。如果放不下了就移动到下一行去。并这一行的高度其实就是最高的控件的高度
[code] if (heightMax < (view.getMeasuredHeight() + params.topMargin + params.bottomMargin)) { heightMax = (view.getMeasuredHeight() + params.topMargin + params.bottomMargin); }
当移动到下一行就将测量的高度加上上一行最高的高度。
[code] measureHeight += heightMax;
并且判断是否是最宽的一行。如果是的话那么就把当前布局的宽度设置成这个宽度。
[code]if (widthMax < measureWidth) { //换成最宽的宽度; widthMax = measureWidth; }
记住在最后一行的时候需要将自己的行的高度添加上去
[code] if(i == (childCount - 1)){ //这个是最后一行并且没到换行的地方需要把这一行的高度加进去 measureHeight += heightMax; }
然后将自己的padding计算上去。
[code] if ((measureWidth + getPaddingLeft()) < widthSize) { measureWidth += getPaddingLeft(); } if ((measureWidth + getPaddingRight()) < widthSize) { measureWidth += getPaddingRight(); } if ((measureHeight + getPaddingTop()) < heightSize) { measureHeight += getPaddingTop(); } if ((measureHeight + getPaddingBottom()) < heightSize) { measureHeight += getPaddingBottom(); }
最后在判断自己的layout_width , layout_height 属性对应的是否是MeasureSpec.EXACTLY,如果是的话就直接使用widthSize,heightSize最大的尺寸了。
[code]if (widthMode == MeasureSpec.EXACTLY) { measureWidth = widthSize; } if (heightMode == MeasureSpec.EXACTLY) { measureHeight = heightSize; }
最后设置尺寸
[code] setMeasuredDimension(measureWidth, measureHeight);
然后我们来看看onLayout函数:
[code] @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { Log.e("xhc","onLayout"); //注意父控件的padding,子空间的margin int childCount = getChildCount(); int paddingLeft = getPaddingLeft(); int paddintRight = getPaddingRight(); int paddingTop = getPaddingTop(); int paddingBottom = getPaddingBottom(); int width = getMeasuredWidth(); int height = getMeasuredHeight(); int currentX = paddingLeft, currentY = paddingTop; int heightMax = 0; for (int i = 0; i < childCount; ++i) { View child = getChildAt(i); MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); int childTotalWidth = params.leftMargin + params.rightMargin + child.getMeasuredWidth(); if ((paddintRight + currentX + childTotalWidth) <= getMeasuredWidth()) { //这一行可以放下 int left = (currentX + params.leftMargin); int top = (currentY + params.topMargin); child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); currentX += (params.leftMargin + child.getMeasuredWidth() + params.rightMargin); if (heightMax < (top + child.getMeasuredHeight() + params.bottomMargin)) { heightMax = (top + child.getMeasuredHeight() + params.bottomMargin); } } else { currentX = paddingLeft; currentY += heightMax; } } }
这里的基本逻辑和onMeasure中一致。判断这一行是否能放下,能放下就放下(阿弥陀佛)
[code]if ((paddintRight + currentX + childTotalWidth) <= getMeasuredWidth()) { //这一行可以放下 int left = (currentX + params.leftMargin); int top = (currentY + params.topMargin); child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); }
不能放下就到下一行去。
[code]else { currentX = paddingLeft; currentY += heightMax; }
好了最后贴出源码地址:
源码下载
加个好友共同学习(不是公众号):
因为小弟水平有限,如果有写的有问题,希望指出。
相关文章推荐
- linux 命令 —— mv
- Spring JPA Hibernate problem: LazyInitializationException: could not initialize proxy no session
- 今天遇到一个乱码问题
- CString转化为int(float) 及 AfxMessageBox 变量显示
- http+SSL
- 【science封面文章】Human-level concept learning through probabilistic program induction
- 123
- iOS 中的正则匹配(工具类方法)
- VS常用快捷键
- VFS分析(一)挂载(持续更新)
- Oracle学习之shared pool--硬解析和软解析
- resharper使用
- mac上安装MySQL
- extern "c"用法解析
- Android Support库——support annotations
- Android Support库——support annotations
- client denied by server configuration
- Python 把矩阵转为list
- JS 页面加载触发事件 document.ready和window.onload的区别
- js版根据经纬度计算多边形面积(墨卡托投影)