Android 流式布局FlowLayout 实现关键字标签
来源:互联网 发布:自考培训机构 知乎 编辑:程序博客网 时间:2024/06/05 18:27
1.介绍
流式布局的应用还是很广泛的,比如搜索热词、关键词标签等,GitHub上已经有很多这样的布局了,但是还是想着自己实现一下,最近一直在学自定义控件,也巩固一下所学的知识。
本文实现的效果如下图所示:
2.思路
- 继承自RelativeLayout,可以直接使用RelativeLayout中的相关属性,本文也可以修改为继承ViewGroup,并不会有什么影响。
- 在onMeasure方法中计算出所有childView的宽和高,然后根据childView的宽和高计算出布局自身的宽和高。
- 在onLayout方法中计算出所有childView的位置并进行布局。
- 封装Line对象,管理每行上的View对象
3.实现
初始化一些属性
public class FlowLayout extends RelativeLayout { // 水平间距,单位为dp private int horizontalSpacing = dp2px(10); // 竖直间距,单位为dp private int verticalSpacing = dp2px(10); // 行的集合 private List<Line> lines = new ArrayList<Line>(); // 当前的行 private Line line; // 当前行使用的空间 private int lineSize = 0; // 关键字大小,单位为sp private int textSize = sp2px(15); // 关键字颜色 private int textColor = Color.BLACK; // 关键字背景框 private int backgroundResource = R.drawable.bg_frame; // 关键字水平padding,单位为dp private int textPaddingH = dp2px(7); // 关键字竖直padding,单位为dp private int textPaddingV = dp2px(4); public FlowLayout(Context context) { this(context, null); } public FlowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.getTheme().obtainStyledAttributes( attrs, R.styleable.FlowLayoutAttrs, defStyleAttr, 0); int count = typedArray.getIndexCount(); for (int i = 0; i < count; i++) { int attr = typedArray.getIndex(i); switch (attr) { case R.styleable.FlowLayoutAttrs_horizontalSpacing: horizontalSpacing = typedArray.getDimensionPixelSize(attr, dp2px(10)); break; case R.styleable.FlowLayoutAttrs_verticalSpacing: verticalSpacing = typedArray.getDimensionPixelSize(attr, dp2px(10)); break; case R.styleable.FlowLayoutAttrs_textSize: textSize = typedArray.getDimensionPixelSize(attr, sp2px(15)); break; case R.styleable.FlowLayoutAttrs_textColor: textColor = typedArray.getColor(attr, Color.BLACK); break; case R.styleable.FlowLayoutAttrs_backgroundResource: backgroundResource = typedArray.getResourceId(attr, R.drawable.bg_frame); break; case R.styleable.FlowLayoutAttrs_textPaddingH: textPaddingV = typedArray.getDimensionPixelSize(attr, dp2px(7)); break; case R.styleable.FlowLayoutAttrs_textPaddingV: verticalSpacing = typedArray.getDimensionPixelSize(attr, dp2px(4)); break; } } typedArray.recycle(); } ...}
onMeasure
首先获取父容器传入的宽高值与测量模式,计算出实际使用的宽和高,遍历所有的childView,对childView进行测量,根据当前行已用的宽度判断是否需要换行,然后累计所有高度,设置布局自身尺寸。
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 实际可以用的宽和高 int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop(); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); // Line初始化 restoreLine(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); // 测量所有的childView int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); if (line == null) { // 创建新一行 line = new Line(); } // 计算当前行已使用的宽度 int measuredWidth = child.getMeasuredWidth(); lineSize += measuredWidth; // 如果使用的宽度小于可用的宽度,这时候childView能够添加到当前的行上 if (lineSize <= width) { line.addChild(child); lineSize += horizontalSpacing; } else { // 换行 newLine(); line.addChild(child); lineSize += child.getMeasuredWidth(); lineSize += horizontalSpacing; } } // 把最后一行记录到集合中 if (line != null && !lines.contains(line)) { lines.add(line); } int totalHeight = 0; // 把所有行的高度加上 for (int i = 0; i < lines.size(); i++) { totalHeight += lines.get(i).getHeight(); } // 加上行的竖直间距 totalHeight += verticalSpacing * (lines.size() - 1); // 加上上下padding totalHeight += getPaddingBottom(); totalHeight += getPaddingTop(); // 设置自身尺寸 // 设置布局的宽高,宽度直接采用父view传递过来的最大宽度,而不用考虑子view是否填满宽度 // 因为该布局的特性就是填满一行后,再换行 // 高度根据设置的模式来决定采用所有子View的高度之和还是采用父view传递过来的高度 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), resolveSize(totalHeight, heightMeasureSpec));}
附上restoreLine与newLine方法
private void restoreLine() { lines.clear(); line = new Line(); lineSize = 0;}private void newLine() { // 把之前的行记录下来 if (line != null) { lines.add(line); } // 创建新的一行 line = new Line(); lineSize = 0;}
Line
封装了每行上的View对象,提供添加与绘制childView的方法。
/** * 管理每行上的View对象 */class Line { // 子控件集合 private List<View> children = new ArrayList<View>(); // 行高 int height; /** * 添加childView * * @param childView 子控件 */ public void addChild(View childView) { children.add(childView); // 让当前的行高是最高的一个childView的高度 if (height < childView.getMeasuredHeight()) { height = childView.getMeasuredHeight(); } } /** * 设置childView的绘制区域 * * @param left 左上角x轴坐标 * @param top 左上角y轴坐标 */ public void layout(int left, int top) { int totalWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); // 当前childView的左上角x轴坐标 int currentLeft = left; for (int i = 0; i < children.size(); i++) { View view = children.get(i); // 设置childView的绘制区域 view.layout(currentLeft, top, currentLeft + view.getMeasuredWidth(), top + view.getMeasuredHeight()); // 计算下一个childView的位置 currentLeft = currentLeft + view.getMeasuredWidth() + horizontalSpacing; } } public int getHeight() { return height; } public int getChildCount() { return children.size(); }}
onLayout
指定所有childView的位置,调用Line对象中的layout方法。
@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); int left = getPaddingLeft(); int top = getPaddingTop(); for (int i = 0; i < lines.size(); i++) { Line line = lines.get(i); line.layout(left, top); // 计算下一行的起点y轴坐标 top = top + line.getHeight() + verticalSpacing; }}
setFlowLayout
用于设置FlowLayout中的内容,并提供点击事件处理。
public void setFlowLayout(List<String> list, final OnItemClickListener onItemClickListener) { for (int i = 0; i < list.size(); i++) { final TextView tv = new TextView(getContext()); // 设置TextView属性 tv.setText(list.get(i)); tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); tv.setTextColor(textColor); tv.setGravity(Gravity.CENTER); tv.setPadding(textPaddingH, textPaddingV, textPaddingH, textPaddingV); tv.setClickable(true); tv.setBackgroundResource(backgroundResource); this.addView(tv, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onItemClickListener.onItemClick(tv.getText().toString()); } }); }}public interface OnItemClickListener { void onItemClick(String content);}
使用方法
- 在项目根目录的build.gradle文件中加入如下代码
maven { url "https://jitpack.io" }
- 在app根目录的buil.gradle文件中加入依赖
compile 'com.github.alidili:FlowLayout:v1.0'
- 在Activity中使用,设置点击事件
相关属性(字体大小、颜色、间距等)可以在布局文件中设置,也可以通过set方法设置。
public class MainActivity extends AppCompatActivity { private FlowLayout flKeyword; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); flKeyword = (FlowLayout) findViewById(R.id.fl_keyword); List<String> list = new ArrayList<>(); list.add("关键词一"); list.add("关键词二"); list.add("关键词三"); list.add("关键词四"); list.add("关键词五"); flKeyword.setFlowLayout(list, new FlowLayout.OnItemClickListener() { @Override public void onItemClick(String content) { Toast.makeText(MainActivity.this, content, Toast.LENGTH_SHORT).show(); } }); }}
- 布局文件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.yang.flowlayoutlibrary.FlowLayout android:id="@+id/fl_keyword" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="10dp" app:itemColor="@color/colorAccent" app:itemSize="15sp" /></RelativeLayout>
4.写在最后
源码已托管到GitHub上,欢迎Fork,觉得还不错就Start一下吧!
点击下载源码
GitHub地址:https://github.com/alidili/FlowLayout
欢迎同学们吐槽评论,如果你觉得本篇博客对你有用,那么就留个言或者顶一下吧(^-^)
3 0
- Android 流式布局FlowLayout 实现关键字标签
- Android 实现FlowLayout流式布局(类似热门标签)
- Android 实现FlowLayout流式布局(类似热门标签)
- android 实现流式布局FlowLayout
- 使用TabLayout实现单行的flowlayout(标签流式布局)
- android自定义view实现流式布局(FlowLayout)和热门标签
- Android 流式布局FlowLayout
- Android流式布局-FlowLayout
- Android流式布局FlowLayout
- Android:FlowLayout流式布局
- Android FlowLayout 流式布局
- Android轻松实现流式布局之FlowLayout
- Android 中实现FlowLayout 布局
- 自定义ViewGroup实现流式布局FlowLayout
- FlowLayout实现自定义的流式布局
- Android自定义流式布局-FlowLayout
- Android之FlowLayout流式布局
- Android自定义流式布局-FlowLayout
- C++ 对象和类(十七)--operator+、private、public、protected、this->指针
- 面向对象程序设计上机练习十(运算符重载)
- 我的控制反转,依赖注入和面向切面编程的理解
- Centos6.6+Jenkins+gitlabs 远程部署项目
- Parajumpers Herren and main America You
- Android 流式布局FlowLayout 实现关键字标签
- TextField随着键盘的弹出上移
- ubuntu 16.04 上 Redis利用持久化进行数据迁移
- sublime控制台以及jshint插件安装
- Nelder–Mead method
- 占位22
- JavaScript事件模型
- Android开发之——FilenameFilter文件过滤器
- Python-smtplib