网上关于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 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);
}
//测量当前控件
@Override
protected 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+horizontalSpacing0){
//当前行没有数据或者现在的宽度+下一个宽度<行宽。不需要换行,直接添加到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));
}
//分配子控件的位置,如果剩余的距离不够使用,则需要换行
@Override
protected 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 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的布局,宽度在原有布局的基础上增加了surplusChild
view.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:
<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
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 datas;
@Override
protected 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() {
@Override
public 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
<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