自己实现FlowLayout来教你自定义ViewGroup
来源:互联网 发布:icloud优化存储空间 编辑:程序博客网 时间:2024/05/17 22:54
关于FlowLayout的实现网上的文章已经太多了,但是,俗话说,实践出真知,对于我这样的新手,自己写一个还是很有必要的,写完发现很多大神们不曾提及的小知识点,在这里还是要分享一下。
首先先看一张典型的View的在viewgroup中的摆放图
这是一个viewgroup和view的标配属性,接下来我们的自定义ViewGroup将围绕这张图展开。
自定义一个完整的viewgroup(支持margin和padding属性),我们需要实现三个函数
边框范围测量函数onMeasure,子view的布局函数onLayout以及配置Params信息的函数generateLayoutParams;
下面来一一完成并解析这些函数
首先generateLayoutParams
@Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); }
很简单,就返回一个MarginLayoutParams对象,这个对象可以帮助我们获取子view的布局信息。
然后是onMeasure,这个函数的作用是对viewgroup所需的宽高进行测量,并决定最后的宽高
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int selfWidth = resolveSize(0, widthMeasureSpec); //获取设置的padding属性 int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int paddingRight = getPaddingRight(); int paddingBottom = getPaddingBottom(); int childLeft = paddingLeft; int childTop = paddingTop; int lineHeight = 0; //通过计算每一个子控件的高度,得到自己的高度 for (int i = 0, childCount = getChildCount(); i < childCount; ++i) { View childView = getChildAt(i); MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams(); childView.measure( getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, layoutParams.width), getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, layoutParams.height)); int childWidth = childView.getMeasuredWidth()+layoutParams.leftMargin+layoutParams.rightMargin; int childHeight = childView.getMeasuredHeight()+layoutParams.bottomMargin+layoutParams.topMargin; lineHeight = Math.max(childHeight, lineHeight); int totalWidth=childLeft+childWidth+paddingRight; if (totalWidth>selfWidth){ childLeft = paddingLeft+childWidth; childTop += lineHeight; lineHeight = childHeight; } else { childLeft += childWidth ; } } int wantedHeight = childTop + lineHeight + paddingBottom; setMeasuredDimension(selfWidth, resolveSize(wantedHeight, heightMeasureSpec)); }
代码的思路就是通过计算每一个view的宽高,得出自己所需要的宽高,最后通过调用setMeasuredDimension来设置所需的宽高。其中resolveSize函数的作用就是根据计算出的宽高和自身的specMode和specSize进行计算得出的值对比,返回一个最合适的值。
最后就是进行view 的位置摆放onLayout函数
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int myWidth = r - l; //获取设置的padding属性 int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int paddingRight = getPaddingRight(); int childLeft = paddingLeft; int childTop = paddingTop; int lineHeight = 0; //根据子控件的宽高,计算子控件应该出现的位置。 for (int i = 0, childCount = getChildCount(); i < childCount; ++i) { View childView = getChildAt(i); if (childView.getVisibility() == View.GONE) { continue; } //通过MarginLayoutParams可以获得子view的margin属性 MarginLayoutParams layoutParams= (MarginLayoutParams) childView.getLayoutParams(); int childWidth = childView.getMeasuredWidth(); int childHeight = childView.getMeasuredHeight(); lineHeight = Math.max(childHeight+layoutParams.bottomMargin, lineHeight); if (childLeft+ childWidth+layoutParams.leftMargin +layoutParams.rightMargin+ paddingRight > myWidth) { childLeft = paddingLeft+layoutParams.leftMargin; childTop += lineHeight+layoutParams.topMargin; lineHeight = childHeight; } if (i==0){childLeft+=layoutParams.leftMargin; childTop+=layoutParams.topMargin;} childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); childLeft += childWidth +layoutParams.rightMargin; } }
代码的总思路很简单(onMeasure也参考这个思路)
if(某行所有的view加起来的总宽度超过viewgroup所能接受的最大宽度){
换行,重置view放置的启始位置(左边从起点开始,top值则为加上上一行行高的值),重置记录的行高
}else{
不换行,将当前的view摆放在前一个view的后面
}
然后是关于其他人博客中没有注明的自己理解的点,
1.margin属性是由控件所在的viewgroup进行测量并绘制的,这也是我们要重写generateLayoutParams的原因,配置以后才能读取子view的margin信息并绘制,padding属性是由view或者viewgroup本身测量并绘制的
2.子view的onLayout函数中的四个值childView.layout(childLeft, childTop, childright, childbottom)不是与margin和padding无关的,也就是刚刚说的,margin和padding都是有专门绘制的,四个点如下图
最后,贴上完整的代码,以及效果图
import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import com.starrynight.huhu.utils.LogUtil;import java.util.ArrayList;import java.util.List;/** * Created by 四脚朝天的卡比兽 on 2016/9/14. */public class FlowLayout extends ViewGroup{ public FlowLayout(Context context) { super(context); } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int selfWidth = resolveSize(0, widthMeasureSpec); int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int paddingRight = getPaddingRight(); int paddingBottom = getPaddingBottom(); int childLeft = paddingLeft; int childTop = paddingTop; int lineHeight = 0; //通过计算每一个子控件的高度,得到自己的高度 for (int i = 0, childCount = getChildCount(); i < childCount; ++i) { View childView = getChildAt(i); MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams(); childView.measure( getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, layoutParams.width), getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, layoutParams.height)); int childWidth = childView.getMeasuredWidth()+layoutParams.leftMargin+layoutParams.rightMargin; int childHeight = childView.getMeasuredHeight()+layoutParams.bottomMargin+layoutParams.topMargin; lineHeight = Math.max(childHeight, lineHeight); int totalWidth=childLeft+childWidth+paddingRight; if (totalWidth>selfWidth){ childLeft = paddingLeft+childWidth; childTop += lineHeight; lineHeight = childHeight; } else { childLeft += childWidth ; } } int wantedHeight = childTop + lineHeight + paddingBottom; setMeasuredDimension(selfWidth, resolveSize(wantedHeight, heightMeasureSpec)); } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int myWidth = r - l; int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int paddingRight = getPaddingRight(); int childLeft = paddingLeft; int childTop = paddingTop; int lineHeight = 0; //根据子控件的宽高,计算子控件应该出现的位置。 for (int i = 0, childCount = getChildCount(); i < childCount; ++i) { View childView = getChildAt(i); if (childView.getVisibility() == View.GONE) { continue; } MarginLayoutParams layoutParams= (MarginLayoutParams) childView.getLayoutParams(); int childWidth = childView.getMeasuredWidth(); int childHeight = childView.getMeasuredHeight(); lineHeight = Math.max(childHeight+layoutParams.bottomMargin, lineHeight); if (childLeft+ childWidth+layoutParams.leftMargin +layoutParams.rightMargin+ paddingRight > myWidth) { childLeft = paddingLeft+layoutParams.leftMargin; childTop += lineHeight+layoutParams.topMargin; lineHeight = childHeight; } if (i==0){childLeft+=layoutParams.leftMargin; childTop+=layoutParams.topMargin;} childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); childLeft += childWidth +layoutParams.rightMargin; } }}
- 自己实现FlowLayout来教你自定义ViewGroup
- Android 自定义ViewGroup 实现FlowLayout
- Android 自定义ViewGroup 实现FlowLayout
- 一个FlowLayout带你学会自定义ViewGroup
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 【Android】 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- 自定义ViewGroup实现流式布局FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- Linux下的 FTP
- 【那些年遇到过的面试题】考虑如何将一个vector 赋给另一个vector
- 71. Simplify Path
- 远程通信的几种选择(RPC,Webservice,RMI,JMS的区别)
- c_str
- 自己实现FlowLayout来教你自定义ViewGroup
- Hibernate flush详解
- Error setting expression 'XXX.XXX' with value '[Ljava.lang.String;@23b0f28f'ssh插入外键字段报错
- hybrid app开发工具
- 关于秒杀业务的相关处理
- win7怎样取消Mysql自动启动?
- Windows常见数据类型
- 视频播放全屏时其它控件的隐藏以及还原
- It‘s just a begining