实现标签的流式布局
来源:互联网 发布:电脑编程好学吗 编辑:程序博客网 时间:2024/06/01 09:06
先上一张我的实现效果图:
想要实现的效果和思路:向这个布局容器中添加的组件填充在一行里,宽度超出该行剩余空间时转到下一行,该行剩余空间由该行的几个组件平分。布局类负责安排每一行的组件和行的纵坐标,行对象负责安排自己行里组件的位置。最后为了美观将这些组件赋予随机的颜色值即可。
1.在attr.xml中定义两个属性,每个组件的水平间距和行间距:
<declare-styleable name="FlowLayout"> <attr name="space_horizontal" format="dimension"></attr> <attr name="space_vertical" format="dimension"></attr> </declare-styleable>
private Context context; private float space_horizontal,space_vertical; //除去两旁空白后的行宽度 private int maxWidth; //储存每一行的列表 private List<Line> mLines = new ArrayList(); //当前行 private Line mCurrentLine = null;
3.从构造器中获取用户给予的属性,这里把一个参数的构造器也写上,让它调用两个参数的方法:
public FlowLayout(Context context) { super(context,null); this.context = context; } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); //获取用户赋予的属性,包括水平和竖直标签的间距 TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.FlowLayout); space_horizontal = array.getDimension(R.styleable.FlowLayout_space_horizontal,0); space_vertical = array.getDimension(R.styleable.FlowLayout_space_vertical,0); array.recycle(); this.context = context; }
4.测量方法,要遍历孩子进行测量,至于测量自己,宽度就是传入的宽度,高度这里定义为所有行加和:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 每次测量之前都先清空集合,不让会覆盖掉以前 mLines.clear(); mCurrentLine = null; // 获取总宽度 int width = MeasureSpec.getSize(widthMeasureSpec); // 计算最大的宽度 maxWidth = width - getPaddingLeft() - getPaddingRight(); // ******************** 测量孩子 ******************** // 遍历获取孩子 int childCount = this.getChildCount(); //Toast.makeText(context,childCount,Toast.LENGTH_SHORT).show(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 测量孩子 measureChild(childView, widthMeasureSpec, heightMeasureSpec); // 测量完需要将孩子添加到管理行的孩子的集合中,将行添加到管理行的集合中 if (mCurrentLine == null) { // 初次添加第一个孩子的时候 mCurrentLine = new Line(maxWidth, space_horizontal); // 添加孩子 mCurrentLine.addView(childView); // 添加行 mLines.add(mCurrentLine); } else { // 行中有孩子的时候,判断时候能添加 if (mCurrentLine.canAddView(childView)) { // 继续往该行里添加 mCurrentLine.addView(childView); } else { // 添加到下一行 mCurrentLine = new Line(maxWidth, space_horizontal); mCurrentLine.addView(childView); mLines.add(mCurrentLine); } } } // ******************** 测量自己 ********************* // 测量自己只需要计算高度,宽度肯定会被填充满的 int height = getPaddingTop() + getPaddingBottom(); for (int i = 0; i < mLines.size(); i++) { // 所有行的高度 height += mLines.get(i).height; } // 所有竖直的间距 height += (mLines.size() - 1) * space_vertical; // 测量 setMeasuredDimension(width, height); }
5.布局方法,确定每一行的纵坐标,也就是把每一行的高度传给行,最终每个组件调用layout方法布局是在行的方法中:
@Override protected void onLayout(boolean changed, int l, int t, int i2, int i3) { // 这里只负责高度的位置,具体的宽度和子孩子的位置让具体的行去管理 l = getPaddingLeft(); t = getPaddingTop(); for (int i = 0; i < mLines.size(); i++) { // 获取行 Line line = mLines.get(i); // 管理 line.layout(t, l); // 更新高度 t += line.height; if (i != mLines.size() - 1) { // 不是最后一条就添加间距 t += space_vertical; } } }6.行对象类,注意添加组件时的判断逻辑,判断是否宽度超出改行剩余空间。每行的高度是根据最高的组件确定:
public class Line { // 定义一个行的集合来存放子View private List<View> views = new ArrayList<>(); // 行的最大宽度 private int maxWidth; // 行中已经使用的宽度 private int usedWidth; // 行的高度 private int height; // 孩子之间的距离 private float space; // 通过构造初始化最大宽度和边距 public Line(int maxWidth, float horizontalSpace) { this.maxWidth = maxWidth; this.space = horizontalSpace; } /** * 往集合里添加孩子 */ public void addView(View view) { int childWidth = view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); // 更新行的使用宽度和高度 if (views.size() == 0) { // 集合里没有孩子的时候 if (childWidth > maxWidth) { usedWidth = maxWidth; height = childHeight; } else { usedWidth = childWidth; height = childHeight; } } else { usedWidth += childWidth + space; height = childHeight > height ? childHeight : height; } // 添加孩子到集合 views.add(view); } /** * 判断当前的行是否能添加孩子 * * @return */ public boolean canAddView(View view) { // 集合里没有数据可以添加 if (views.size() == 0) { return true; } // 最后一个孩子的宽度大于剩余宽度就不添加 if (view.getMeasuredWidth() > (maxWidth - usedWidth - space)) { return false; } // 默认可以添加 return true; } /** * 指定孩子显示的位置 * * @param t * @param l */ public void layout(int t, int l) { // 平分剩下的空间 int avg = (maxWidth - usedWidth) / views.size(); // 循环指定孩子位置 for (View view : views) { // 获取宽高 int measuredWidth = view.getMeasuredWidth(); int measuredHeight = view.getMeasuredHeight(); // 重新测量 view.measure(MeasureSpec.makeMeasureSpec(measuredWidth + avg, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)); // 重新获取宽度值 measuredWidth = view.getMeasuredWidth(); int top = t; int left = l; int right = measuredWidth + left; int bottom = measuredHeight + top; // 指定位置 view.layout(left, top, right, bottom); // 更新数据 l += measuredWidth + space; } } }
——————————————————————————————————————————————————————————————————
FlowLayout类完整代码:
import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import android.widget.Toast;import java.util.ArrayList;import java.util.List;import java.util.Random;/** * 流式布局,把长宽不一的子项流式排列 * Created by lenovo on 2016/10/16. */public class FlowLayout extends ViewGroup{ private Context context; private float space_horizontal,space_vertical; //除去两旁空白后的行宽度 private int maxWidth; //储存每一行的列表 private List<Line> mLines = new ArrayList(); //当前行 private Line mCurrentLine = null; public FlowLayout(Context context) { super(context,null); this.context = context; } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); //获取用户赋予的属性,包括水平和竖直标签的间距 TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.FlowLayout); space_horizontal = array.getDimension(R.styleable.FlowLayout_space_horizontal,0); space_vertical = array.getDimension(R.styleable.FlowLayout_space_vertical,0); array.recycle(); this.context = context; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 每次测量之前都先清空集合,不让会覆盖掉以前 mLines.clear(); mCurrentLine = null; // 获取总宽度 int width = MeasureSpec.getSize(widthMeasureSpec); // 计算最大的宽度 maxWidth = width - getPaddingLeft() - getPaddingRight(); // ******************** 测量孩子 ******************** // 遍历获取孩子 int childCount = this.getChildCount(); //Toast.makeText(context,childCount,Toast.LENGTH_SHORT).show(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 测量孩子 measureChild(childView, widthMeasureSpec, heightMeasureSpec); // 测量完需要将孩子添加到管理行的孩子的集合中,将行添加到管理行的集合中 if (mCurrentLine == null) { // 初次添加第一个孩子的时候 mCurrentLine = new Line(maxWidth, space_horizontal); // 添加孩子 mCurrentLine.addView(childView); // 添加行 mLines.add(mCurrentLine); } else { // 行中有孩子的时候,判断时候能添加 if (mCurrentLine.canAddView(childView)) { // 继续往该行里添加 mCurrentLine.addView(childView); } else { // 添加到下一行 mCurrentLine = new Line(maxWidth, space_horizontal); mCurrentLine.addView(childView); mLines.add(mCurrentLine); } } } // ******************** 测量自己 ********************* // 测量自己只需要计算高度,宽度肯定会被填充满的 int height = getPaddingTop() + getPaddingBottom(); for (int i = 0; i < mLines.size(); i++) { // 所有行的高度 height += mLines.get(i).height; } // 所有竖直的间距 height += (mLines.size() - 1) * space_vertical; // 测量 setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int l, int t, int i2, int i3) { // 这里只负责高度的位置,具体的宽度和子孩子的位置让具体的行去管理 l = getPaddingLeft(); t = getPaddingTop(); for (int i = 0; i < mLines.size(); i++) { // 获取行 Line line = mLines.get(i); // 管理 line.layout(t, l); // 更新高度 t += line.height; if (i != mLines.size() - 1) { // 不是最后一条就添加间距 t += space_vertical; } } } /** * 内部类,行管理器,管理每一行的孩子 */ public class Line { // 定义一个行的集合来存放子View private List<View> views = new ArrayList<>(); // 行的最大宽度 private int maxWidth; // 行中已经使用的宽度 private int usedWidth; // 行的高度 private int height; // 孩子之间的距离 private float space; // 通过构造初始化最大宽度和边距 public Line(int maxWidth, float horizontalSpace) { this.maxWidth = maxWidth; this.space = horizontalSpace; } /** * 往集合里添加孩子 */ public void addView(View view) { int childWidth = view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); // 更新行的使用宽度和高度 if (views.size() == 0) { // 集合里没有孩子的时候 if (childWidth > maxWidth) { usedWidth = maxWidth; height = childHeight; } else { usedWidth = childWidth; height = childHeight; } } else { usedWidth += childWidth + space; height = childHeight > height ? childHeight : height; } // 添加孩子到集合 views.add(view); } /** * 判断当前的行是否能添加孩子 * * @return */ public boolean canAddView(View view) { // 集合里没有数据可以添加 if (views.size() == 0) { return true; } // 最后一个孩子的宽度大于剩余宽度就不添加 if (view.getMeasuredWidth() > (maxWidth - usedWidth - space)) { return false; } // 默认可以添加 return true; } /** * 指定孩子显示的位置 * * @param t * @param l */ public void layout(int t, int l) { // 平分剩下的空间 int avg = (maxWidth - usedWidth) / views.size(); // 循环指定孩子位置 for (View view : views) { // 获取宽高 int measuredWidth = view.getMeasuredWidth(); int measuredHeight = view.getMeasuredHeight(); // 重新测量 view.measure(MeasureSpec.makeMeasureSpec(measuredWidth + avg, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)); // 重新获取宽度值 measuredWidth = view.getMeasuredWidth(); int top = t; int left = l; int right = measuredWidth + left; int bottom = measuredHeight + top; // 指定位置 view.layout(left, top, right, bottom); // 更新数据 l += measuredWidth + space; } } }}
然后在活动布局中加入这个布局组件,往里面加组件就可以了。
0 0
- 实现标签的流式布局
- 使用TabLayout实现单行的flowlayout(标签流式布局)
- Android 流式布局-动态标签实现
- 安卓中使用流式布局实现标签
- Android中常见的热门标签的流式布局的实现——云标签
- 热门标签流式布局的实现--防网易云音乐热门标签
- Android中常见的热门标签的流式布局的实现,类似购物的尺寸标签
- 标签流式布局
- 标签流式布局
- 流式布局--标签
- Android中常见的热门标签的流式布局的实现
- Android中常见的热门标签的流式布局的实现
- Android中常见的热门标签的流式布局的实现
- Android中常见的热门标签的流式布局的实现
- Android中常见的热门标签的流式布局的实现
- Android中常见的热门标签的流式布局的实现
- Android中常见的热门标签的流式布局的实现
- Android中常见的热门标签的流式布局的实现
- Tomcat与Web程序结构与Http协议
- Java中输入一个数 然后进行因式分解 例如:90=2*3*3*5
- 父类构造方法、父类静态方法、父类一般方法、子类构造方法、子类静态方法、子类一般方法调用顺序
- 在CCS下使用终端
- 【数据库原理】“锁”总结
- 实现标签的流式布局
- 剑指Offer面试题60:把二叉树打印成多行 Java实现
- Javascript(五)Javascript基础(浏览器对象BOM)
- 交换机access和trunk的一些小结
- Java中ATM机系统
- Python脚本自动生成相似的Cpp类
- spring四种依赖注入方式
- 【Unity闲谈】在Hierarchy中显示Component的图标
- A004-移位范围超过变量宽度-(ques=1)