Android FlowLayout实现热门标签功能
来源:互联网 发布:如何看待奚梦瑶 知乎 编辑:程序博客网 时间:2024/06/05 05:16
FlowLayout实现热门标签的功能想必大家都见过,有的为搜索的历史记录,有的则是一些推荐等等。总之热门标签在很多应用里面都有使用,先看一下实现的效果图
下面的一张是截取的淘宝搜索的效果
那么我们如何实现上面的效果呢?我实现的效果是充满屏宽状态的,而淘宝的则是没有充满屏宽的。如何实现充满屏宽其实也不是很难。
下面我们就来探讨一下如何实现:
首页我们需要自定义一个控件也就是我们说的FlowLayout流式布局。实现这样的布局我们要注意以下几点:
1.添加子控件的时候是否超出边界,如果超出边界需要换行。
2.就是要考虑我们间距和屏幕的边界值,如果我们最后一个标签的宽度+前面的标签的宽度+前面标签之间的间距=屏幕的宽度,此时是没有最后一个标签与屏幕右边的边距的也就是下面的情况
这种情况怎么办呢?就是需要最后一个标签移到下一行显示,剩余的空间有剩余的几个标签平分。
3.如果单独标签的长度过长已经超出屏幕,那么这个标签的宽度就要压缩到跟屏幕的宽度相同。
为了防止上面的三种情况的发生我们需要在onMeasure(int widthMeasureSpec, int heightMeasureSpec)这个方法里面获取屏幕的宽度和高度
int availableWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight() - getPaddingLeft(); int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
以上代码获取的是可用空间的宽和高,因为我们一般设置标签的时候会设置一些边距,关于上面的代码的不懂的可以点击查看自定义控件里面有详解。
获取到屏幕的可用的宽度以后,我们就需要根据我们自己标签的宽度和可用的宽度进行比较。所以我们还需要获取子控件的宽度和高度,实现代码如下
final View child = getChildAt(i);int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(availableWidth , widthMode== MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST: widthMode);childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(availableHeight, heightMode== MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST: heightMode);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
第一种情况的发生,也就是添加子控件的时候超出边界的判断
int childWidth = child.getMeasuredWidth(); mHaveUsedWidth += childWidth; if (mHaveUsedWidth <= availableWidth ) { mTagLine.addView(child);// 添加子控件 mHaveUsedWidth += mHSpac;// 加上间隔 if (mHaveUsedWidth >= availableWidth ) { addLine(); } }
上面的代码也包含了第二种情况mHaveUsedWidth >= availableWidth 就是说明如果加上间距等于可用空间,第三种情况的发生,也就是一个子控件的宽度很长已经超出了屏幕的宽度
if (mTagLine.getViewCount() == 0) { mTagLine.addView(child); addLine();}else { addLine(); mTagLine.addView(child); mHaveUsedWidth += childWidth + mHSpac; }
判断mTagLine.getViewCount() == 0说明上一行已经被子控件占满,而下个子控件刚好是新的一行的第一个子控件,我们已经知道下一个子控件的长度已经超出了屏幕的宽度,所以下一个子控件肯定是要在新增加的那一行里面,新增加的那一行的子控件的数量当然也就是0了。else里面代码的意思就是此行已经有子控件了,因为这个子控件的长度大于屏幕的宽度,在任何一行上面只要有子控件,不论子控件的长度多小,都需要换行。
mTagLine.addView(child);的作用就是强制这个子控件在这一行。
addLine()的作用就是新增加一行,实现代码如下:
private void addLine() { mTagLines.add(mTagLine); mTagLine = new TagLine(); mHaveUsedWidth = 0; }
TagLine是一个类代表的是一行,具体代码如下:
private class TagLine { int mAllChildWidth = 0;// 该行中所有的子控件加起来的宽度 int mChildHeight = 0;// 子控件的高度 List<View> viewList= new ArrayList<View>(); public void addView(View view) {// 添加子控件 viewList.add(view); mAllChildWidth += view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); mChildHeight = childHeight;// 行的高度当然是有子控件的高度决定了 } public int getViewCount() { return viewList.size(); } public void layoutView(int left, int top) { int childCount = getViewCount(); //除去左右边距后可以使用的宽度 int validWidth= getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); // 除了子控件以及子控件之间的间距后剩余的空间 int remainWidth = validWidth- mAllChildWidth - mHSpac * (childCount - 1); if (remainWidth >= 0) { int divideSpac = (int) (remainWidth / childCount + 0.5); for (int i = 0; i < childCount; i++) { final View view = viewList.get(i); int childWidth = view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); // 把剩余的空间平均分配到每个子控件上面 childWidth = childWidth + divideSpac; view.getLayoutParams().width = childWidth; // 由于平均分配剩余空间导致子控件的长度发生了变化,需要重新测量 int widthMeasureSpec = MeasureSpec.makeMeasureSpec( childWidth, MeasureSpec.EXACTLY); int heightMeasureSpec = MeasureSpec.makeMeasureSpec( childHeight, MeasureSpec.EXACTLY); view.measure(widthMeasureSpec, heightMeasureSpec); // 设置子控件的位置 view.layout(left, top, left + childWidth, top + childHeight); left += childWidth + mHSpac; // 获取到的left值是下一个子控件的左边所在的位置 } } else { if (childCount == 1) {//这一种就是一行只有一个子控件的情况 View view = viewList.get(0); view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight()); } } } }
当然了,有时候标签平分后感觉控件不是那么的美观,不想平分剩下的空间怎么办?也就是下面的情况
要想实现上面的效果很简单,就是去掉两行代码即可。这两行代码就是关于平分剩余空间的
int divideSpac = (int) (remainWidth / childCount + 0.5);childWidth = childWidth + divideSpac;
divideSpac 的值就是剩余的空间平分到每个控件的值,childWidth 这个值就是自身的宽度加上平分的空间,其实就是我们所看到的平分后的控件的宽度。
下面就是全部的代码了
MainActivity 类
package com.lyxrobert.flowlayout;import android.app.Activity;import android.graphics.Color;import android.graphics.drawable.StateListDrawable;import android.os.Bundle;import android.util.TypedValue;import android.view.Gravity;import android.view.View;import android.widget.TextView;public class MainActivity extends Activity { private FlowLayout flowLayout; private ClearEditText et_clear; private String[] data = new String[]{"全部","这是","测试标签", "这是测试标签","FlowLayout","衣服","鞋子", "春","夏","深秋","寒冬", "测一下看看效果如何","心情还不错哦","这是测试标签","这是测试标签", "这是测试标签","受益匪浅啊","123456789","电话号码"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initView() { flowLayout = (FlowLayout) findViewById(R.id.fl); et_clear = (ClearEditText) findViewById(R.id.et_clear); } private void initData() { int padding = dip2px(5); flowLayout.setPadding(padding, padding, padding, padding);// 设置内边距 for (int i = 0; i < data.length; i++) { final String tag = data[i]; TextView tv = new TextView(this); tv.setText(tag); tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); tv.setPadding(padding, padding, padding, padding); tv.setGravity(Gravity.CENTER); int color = 0xffcecece;// 按下后偏白的背景色 StateListDrawable selector; if (i==0){ tv.setTextColor(Color.WHITE); tv.setEnabled(false); selector = DrawableUtils.getSelector(false,Color.parseColor("#2c90d7"), color, dip2px(30)); }else { selector = DrawableUtils.getSelector(true,Color.WHITE, color, dip2px(30)); } tv.setBackgroundDrawable(selector); flowLayout.addView(tv); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { et_clear.setText(tag); et_clear.setSelection(tag.trim().length()); } }); }} public int dip2px(float dip) { float density = this.getResources().getDisplayMetrics().density; return (int) (dip * density + 0.5f); }}
FlowLayout类
package com.lyxrobert.flowlayout;import java.util.ArrayList;import java.util.List;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;public class FlowLayout extends ViewGroup { /** 横向间隔 */ private int mHSpac = 0; /** 纵向间隔 */ private int mVSpac = 0; /** 当前行已用的宽度*/ private int mHaveUsedWidth = 0; /** 每一行的集合 */ private final List<TagLine> mTagLines = new ArrayList<TagLine>(); private TagLine mTagLine = null; public FlowLayout(Context context) { super(context); } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); setHorizontalSpacing(dip2px(5)); setVerticalSpacing(dip2px(5)); } public int dip2px(float dip) { float density = this.getResources().getDisplayMetrics().density; return (int) (dip * density + 0.5f); } public void setHorizontalSpacing(int spacing) { if (mHSpac != spacing) { mHSpac = spacing; requestLayout(); } } public void setVerticalSpacing(int spacing) { if (mVSpac != spacing) { mVSpac = spacing; requestLayout(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int availableWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight() - getPaddingLeft(); int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); resetLine();// 将行的状态重置为最原始的状态,因为新的一行的数据跟以往的无关 final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(availableWidth , widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( availableHeight, heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode); // 测量子控件 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); if (mTagLine == null) { mTagLine = new TagLine(); } int childWidth = child.getMeasuredWidth(); mHaveUsedWidth += childWidth;// 增加使用的宽度 if (mHaveUsedWidth <= availableWidth ) {// 已经使用的宽度小于可用宽度,说明还有剩余空间,该子控件添加到这一行。 mTagLine.addView(child);// 添加子控件 mHaveUsedWidth += mHSpac;// 加上间距 if (mHaveUsedWidth >= availableWidth ) {// 加上间距后已经使用的宽度大于等于可用宽度,说明这一行已满或者已经超出需要换行 addLine(); } } else { //说明上一行已经被子控件占满,而下个子控件刚好是新的一行的第一个子控件 if (mTagLine.getViewCount() == 0) { mTagLine.addView(child); addLine(); } else { //因为这个子控件的长度大于屏幕的宽度,在任何一行上面只要有子控件,不论子控件的长度多小,都需药换行 addLine(); mTagLine.addView(child); mHaveUsedWidth += childWidth + mHSpac; } } } if (mTagLine != null && mTagLine.getViewCount() > 0 && !mTagLines.contains(mTagLine)) { //此段代码的作用是为了防止因最后一行代码的子控件未占满空间,但是毕竟也是一行,所以也要添加到行的集合里面 mTagLines.add(mTagLine); } int totalWidth = MeasureSpec.getSize(widthMeasureSpec); int totalHeight = 0; final int size = mTagLines.size(); for (int i = 0; i < size; i++) {// 加上所有行的高度 totalHeight += mTagLines.get(i).mChildHeight; } totalHeight += mVSpac * (size - 1);// 加上所有间距的高度 totalHeight += getPaddingTop() + getPaddingBottom();// 加上padding setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int left = getPaddingLeft();// 获取最初的左上点 int top = getPaddingTop(); final int linesCount = mTagLines.size(); for (int i = 0; i < linesCount; i++) { final TagLine oneLine = mTagLines.get(i); oneLine.layoutView(left, top);// 设置每一行所在的位置 top += oneLine.mChildHeight + mVSpac;// 这个top的值其实就是下一个的上顶点值 } } /** 将行的状态重置为最原始的状态*/ private void resetLine() { mTagLines.clear(); mTagLine = new TagLine(); mHaveUsedWidth = 0; } /** 新增加一行 */ private void addLine() { mTagLines.add(mTagLine); mTagLine = new TagLine(); mHaveUsedWidth = 0; } /** * 代表着一行,封装了一行所占高度,该行子View的集合,以及所有View的宽度总和 */ private class TagLine { int mAllChildWidth = 0;// 该行中所有的子控件加起来的宽度 int mChildHeight = 0;// 子控件的高度 List<View> viewList= new ArrayList<View>(); public void addView(View view) {// 添加子控件 viewList.add(view); mAllChildWidth += view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); mChildHeight = childHeight;// 行的高度当然是有子控件的高度决定了 } public int getViewCount() { return viewList.size(); } public void layoutView(int left, int top) { int childCount = getViewCount(); //除去左右边距后可以使用的宽度 int validWidth= getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); // 除了子控件以及子控件之间的间距后剩余的空间 int remainWidth = validWidth- mAllChildWidth - mHSpac * (childCount - 1); if (remainWidth >= 0) { int divideSpac = (int) (remainWidth / childCount + 0.5); for (int i = 0; i < childCount; i++) { final View view = viewList.get(i); int childWidth = view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); // 把剩余的空间平均分配到每个子控件上面 childWidth = childWidth + divideSpac; view.getLayoutParams().width = childWidth; // 由于平均分配剩余空间导致子控件的长度发生了变化,需要重新测量 int widthMeasureSpec = MeasureSpec.makeMeasureSpec( childWidth, MeasureSpec.EXACTLY); int heightMeasureSpec = MeasureSpec.makeMeasureSpec( childHeight, MeasureSpec.EXACTLY); view.measure(widthMeasureSpec, heightMeasureSpec); // 设置子控件的位置 view.layout(left, top, left + childWidth, top + childHeight); left += childWidth + mHSpac; // 获取到的left值是下一个子控件的左边所在的位置 } } else { if (childCount == 1) {//这一种就是一行只有一个子控件的情况 View view = viewList.get(0); view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight()); } } } }}
布局文件
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.lyxrobert.flowlayout.ClearEditText android:id="@+id/et_clear" android:layout_margin="10dp" android:paddingLeft="15dp" android:paddingRight="15dp" android:textSize="18dp" android:textColor="@android:color/white" android:background="@drawable/search_bg" android:layout_width="match_parent" android:layout_height="40dp"/> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <com.lyxrobert.flowlayout.FlowLayout android:id="@+id/fl" android:layout_width="match_parent" android:layout_height="match_parent" > </com.lyxrobert.flowlayout.FlowLayout> </ScrollView></LinearLayout>
如有疑问欢迎留言
扫一扫关于个人公众号
点击下载源码
- Android FlowLayout实现热门标签功能
- Android 实现FlowLayout流式布局(类似热门标签)
- Android 实现FlowLayout流式布局(类似热门标签)
- android自定义view实现流式布局(FlowLayout)和热门标签
- 评价标签FlowLayout实现
- FlowLayout流式布局实现热门搜索,发帖加标签效果
- 自定义FlowLayout,android flowLayout实现
- 解决:Android中常见的热门标签的流式布局flowlayout不能wrap_content
- 热门标签功能。
- Android 热门标签 瀑布流实现
- Android 自定义ViewGroup之实现FlowLayout-标签流容器
- Android 自定义ViewGroup之实现FlowLayout-标签流容器
- Android 流式布局FlowLayout 实现关键字标签
- Android自定义实现FlowLayout
- Android 实现自定义FlowLayout
- Android 自定义控件:打造流布局实现热门搜索标签
- Android 自定义控件:打造流布局实现热门搜索标签
- 自定义FlowLayout实现标签快捷输入框
- iOS自动打包(敲一下enter键,完成iOS的打包工作)
- Android HandlerThread 完全解析
- ArcGIS宗地结构——编辑入门
- JS 正则表达式基础知识详解与回顾
- Trafodion 如何升级
- Android FlowLayout实现热门标签功能
- python修改文件(fileinput)
- 数据结构期末总结
- Git综述
- Ubuntu 14.04 ROS android
- Nginx+uWSGI+Django在Ubuntu下的部署
- 面试题目详解
- LeetCode每日一题——231. Power of Two
- mysqlbinlog 导出操作日志