网上关于FlowLayout的文章有很多,大部分都是右侧空白不固定:
但是不想我想要的效果,修改了一下,先来看看效果图。
如果你对FlowLayout还不了解,可以看看鸿洋大神的文章:Android 自定义ViewGroup 实战篇 -> 实现FlowLayout。想一想,其实在设置每个子类的宽度的时候,将剩余宽度平均分配给每个子控件便可以实现我要的效果。
嗯,先上FlowLayout文件,其实主要是在layout方法中做了修改。
package com.android.flowlayout;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;import java.util.ArrayList;
import java.util.List;/*** 文字瀑布流,瀑布流中每个子控件是textview,如果不是,请重新写layout方法,将返回的子控件定义为你的控件类型,* Created by wu on 2015/11/12.*/
public class FlowLayout extends ViewGroup {private List<Line> mLines = new ArrayList<>();private Line currentLine;//当前行private int usedWidth = 0;//当前行已经使用的宽度private int horizontalSpacing;//水平的间隔private int verticalSpacing;//垂直的间隔private int width;//控件的宽度private int height;//控件的高度private Context mContext;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);this.mContext = context;horizontalSpacing=UiUtils.dp2px(context,13);verticalSpacing=UiUtils.dp2px(context,13);}//测量当前控件@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//获取当前容器的宽高模式和大小mLines.clear();currentLine = null;usedWidth = 0;int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);width = MeasureSpec.getSize(widthMeasureSpec)-getPaddingLeft()-getPaddingRight();height = MeasureSpec.getSize(heightMeasureSpec)-getPaddingTop()-getPaddingBottom();int childWidthMode;int childHeightMode;//为了测量每个子控件,需要指定每个子控件的测量规则childWidthMode = widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode;childHeightMode = heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode;int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, childWidthMode);int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, childHeightMode);currentLine = new Line();//创建了新的一行(第一行)for (int i = 0; i < getChildCount(); i++) {//测量子控件View child = getChildAt(i);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);int measuredWidth = child.getMeasuredWidth();//获得子控件的宽度if(usedWidth+measuredWidth+horizontalSpacing<width ||currentLine.getChildCount()==0){//当前行没有数据或者现在的宽度+下一个宽度<行宽。不需要换行,直接添加到current中。currentLine.addChild(child);usedWidth+=measuredWidth;usedWidth+=horizontalSpacing;}else{newLine();currentLine.addChild(child);usedWidth+=measuredWidth;usedWidth+=horizontalSpacing;}}if (!mLines.contains(currentLine)) {//添加最后一行mLines.add(currentLine);Log.d("FlowLayout", "currentLine.getChildCount():" + currentLine.getChildCount());}int totalHeight = 0;for (Line line : mLines) {totalHeight += line.getHeight();}totalHeight += ((mLines.size() - 1) * verticalSpacing)+getPaddingTop()+getPaddingBottom();setMeasuredDimension(width+getPaddingLeft()+getPaddingRight(), resolveSize(totalHeight, heightMeasureSpec));}//分配子控件的位置,如果剩余的距离不够使用,则需要换行@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {l+=getPaddingLeft();t+=getPaddingTop();for (int i = 0; i < mLines.size(); i++) {Line line = mLines.get(i);line.layout(l, t);t += line.getHeight() + verticalSpacing;//每一行左上角的t值都会改变}}/*** 每一个行的类*/private class Line {int height = 0;List<View> children = new ArrayList<>();int total = 0;/*** 添加一个子控件** @param child*/public void addChild(View child) {children.add(child);if (child.getMeasuredHeight() > height) {height = child.getMeasuredHeight();}total += child.getMeasuredWidth();}/*** 获取子控件的数量** @return*/public int getChildCount() {return children.size();}public int getHeight() {return height;}/*** 指定行的左上角位置,其子类的位置由该函数确定** @param l 左侧位置* @param t 顶部位置*/public void layout(int l, int t) {total += horizontalSpacing * (children.size() - 1);//现有子控件所占有的宽度int surplusChild = 0;int surplus = width - total;//右侧剩余的宽度surplusChild = surplus / children.size();//右侧剩余宽度平分给各个控件for (int i = 0; i < children.size(); i++) {//将每一个子TextView取出来TextView view = (TextView) children.get(i);//设置每个子TextView的布局,宽度在原有布局的基础上增加了surplusChildview.layout(l, t, l + view.getMeasuredWidth()+surplusChild, t + view.getMeasuredHeight());//为子View的字体设置居中,此步骤不能在给layout添加view的时候,给view设置gravity属性,只能在这里设置view.setGravity(Gravity.CENTER);String text=view.getText().toString();if(text!=null){//如果此时textview的文字已经绘制完成,因为我们重新layout,会导致文字不居中,重新获取文字,并设置,view.setText(text);}//更新下一个子View的左侧的位置l += view.getMeasuredWidth()+surplusChild;l += verticalSpacing;}}}/*** 创建新的行*/public void newLine() {mLines.add(currentLine);currentLine = new Line();usedWidth = 0;}
}
我们的xml主布局文件其实很简单。
activity_main:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><ScrollView
android:id="@+id/scrollView"android:layout_width="match_parent"android:layout_height="match_parent"/>
</RelativeLayout>
下面来看看我们在activity中是如何使用我们的这个FlowLayout的。
MainActivity.java
datas;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initViews();initDatas();FlowLayout flowLayout = new FlowLayout(this);int padding=UiUtils.dp2px(this,13);flowLayout.setPadding(padding,padding,padding,padding);Drawable pressDrawable=DrawableUtils.createShape(this,0xffcecece);for (int i = 0; i < datas.size(); i++) {TextView textView = new TextView(this);//设置textview未点击时的背景,圆角+随机颜色,通过xml设置+代码实现textView.setBackgroundResource(R.drawable.text_bg);//生成随机颜色,为了防止产生黑色或者白色,设定一定的范围int color= Color.rgb(new Random().nextInt(200) + 20, new Random().nextInt(200) + 20, new Random().nextInt(200) + 20);GradientDrawable drawable= (GradientDrawable) textView.getBackground();//将生成的随机色赋值给背景色drawable.setColor(color);//设置背景为状态选择器textView.setBackgroundDrawable(new DrawableUtils().creatStateListDrawable(pressDrawable, drawable));textView.setText(datas.get(i));final int finalI = i;textView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(MainActivity.this, datas.get(finalI), Toast.LENGTH_SHORT).show();}});textView.setGravity(Gravity.CENTER);textView.setTextColor(Color.WHITE);flowLayout.addView(textView);}scrollView.addView(flowLayout);}/*** 生成要显示的数据*/private void initDatas() {String[] strs=new String[]{"QQ","视频","放开那三国","电子书","酒店","单机","小说","斗地主","优酷","网游","WIFI万能钥匙","播放器","捕鱼达人2","机票","游戏","熊出没之熊大快跑","美图秀秀","浏览器","单机游戏","我的世界","电影电视","QQ空间","旅游","免费游戏","2048","刀塔传奇","壁纸","节奏大师","锁屏","装机必备","天天动听","备份","网盘","海淘网","大众点评","爱奇艺视频","腾讯手机管家","百度地图","猎豹清理大师","谷歌地图","hao123上网导航","京东","youni有你","万年历-农历黄历","支付宝钱包"};datas=new ArrayList<>(Arrays.asList(strs));}private void initViews() {this.scrollView = (ScrollView) findViewById(R.id.scrollView);}
}
" data-snippet-id="ext.bd8139366275f1dcc899ee363c33ef9b" data-snippet-saved="false" data-codota-status="done">package com.android.testflowlayout;import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;public class MainActivity extends AppCompatActivity {private android.widget.ScrollView scrollView;private List<String> datas;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initViews();initDatas();FlowLayout flowLayout = new FlowLayout(this);int padding=UiUtils.dp2px(this,13);flowLayout.setPadding(padding,padding,padding,padding);Drawable pressDrawable=DrawableUtils.createShape(this,0xffcecece);for (int i = 0; i < datas.size(); i++) {TextView textView = new TextView(this);//设置textview未点击时的背景,圆角+随机颜色,通过xml设置+代码实现textView.setBackgroundResource(R.drawable.text_bg);//生成随机颜色,为了防止产生黑色或者白色,设定一定的范围int color= Color.rgb(new Random().nextInt(200) + 20, new Random().nextInt(200) + 20, new Random().nextInt(200) + 20);GradientDrawable drawable= (GradientDrawable) textView.getBackground();//将生成的随机色赋值给背景色drawable.setColor(color);//设置背景为状态选择器textView.setBackgroundDrawable(new DrawableUtils().creatStateListDrawable(pressDrawable, drawable));textView.setText(datas.get(i));final int finalI = i;textView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(MainActivity.this, datas.get(finalI), Toast.LENGTH_SHORT).show();}});textView.setGravity(Gravity.CENTER);textView.setTextColor(Color.WHITE);flowLayout.addView(textView);}scrollView.addView(flowLayout);}/*** 生成要显示的数据*/private void initDatas() {String[] strs=new String[]{"QQ","视频","放开那三国","电子书","酒店","单机","小说","斗地主","优酷","网游","WIFI万能钥匙","播放器","捕鱼达人2","机票","游戏","熊出没之熊大快跑","美图秀秀","浏览器","单机游戏","我的世界","电影电视","QQ空间","旅游","免费游戏","2048","刀塔传奇","壁纸","节奏大师","锁屏","装机必备","天天动听","备份","网盘","海淘网","大众点评","爱奇艺视频","腾讯手机管家","百度地图","猎豹清理大师","谷歌地图","hao123上网导航","京东","youni有你","万年历-农历黄历","支付宝钱包"};datas=new ArrayList<>(Arrays.asList(strs));}private void initViews() {this.scrollView = (ScrollView) findViewById(R.id.scrollView);}
}
嗯,个人感觉说明已经很详细了。最后还有一个简单的圆角背景图,和两个辅助类。
text_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="rectangle"><corners android:radius="5dp"/><solid android:color="#000000"/><padding android:bottom="4dp"android:top="4dp"android:left="7dp"android:right="7dp"/>
</shape>
UiUtils.java
package com.android.testflowlayout;import android.content.Context;
import android.util.TypedValue;/*** UI相关的辅助类* Created by wu on 2015/11/6.*/
public class UiUtils {/** @param context* @param dpVal* @return*/public static int dp2px(Context context,float dpVal){return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpVal,context.getResources().getDisplayMetrics());}
}
DrawableUtils.java
package com.android.testflowlayout;import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;/*** Created by wu on 2015/11/12.*/
public class DrawableUtils {/*** 生成圆角图片* @param context* @param color* @return*/public static Drawable createShape(Context context, int color) {GradientDrawable drawable=new GradientDrawable();drawable.setCornerRadius(UiUtils.dp2px(context,5));drawable.setColor(color);return drawable;}/*** 生成selector,动态设置* @param pressedDrawable 按下时的drawable* @param normalDrawable 正常状态是的drawable* @return*/public static Drawable creatStateListDrawable(Drawable pressedDrawable,Drawable normalDrawable){StateListDrawable drawable=new StateListDrawable();drawable.addState(new int[]{android.R.attr.state_pressed},pressedDrawable);drawable.addState(new int[]{},normalDrawable);return drawable;}
}
所有的文件基本上都在这儿了。
欢迎大家fork。
https://github.com/kailaisi/FlowLayout