先上效果图:2种情况:1、显示很多行,一屏放不下;2、显示指定行数(以2行为例)
1、
以第一种情况讲解,如果用到第二种情况,参见FlowLayout文件中的TODO标记上的讲解。如果一行已经放进来3个数据了,第4要放进来,就超出屏幕宽度,不放进来,就有留白。这时候,处理方法是:不把第4个数据放进来。把留白平分3份,给之前的每个数据加一点宽度,这样好看。
注:这里用了随机生成颜色,每次重新加载,看到的颜色都不一样
实现步骤:
1、将创建FlowLayout文件,将下面源码复制进去
package com.chen.demo;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
/* * 整体思路: 自定义FlowLayout: 1.根据list的size往里面动态添加TextView 2.定义变量: * a.horizontalSpacing:子view间的水平间距 b.verticalSpacing: line间的垂直间距 * c.lineList:用于存放每个line 3.定义Line类:用来记录当前行中的子view,并且提供width和height; * width:表示所有子view的宽加上中间的空隙值,在每次添加子view的时候都会更新 height:始终等于当前行中最高子view的高度 * 4.遍历所有子view过程并判断如下: a.先测量当前childView,保证能获取到宽度和高度 * b.如果当前line中还没子view,则直接放入,不用判断长度 * c.当前line中已经有子view的情况:如果line的宽度+horizontalSpacing+child.getMeasuredWidth * 大于当前总宽度则立即存储当前line,然后重新new Line(),并将子view加入到new的Line中; * 如果不大于总宽度,则将当前childView放入line中; * d.注意:每次往line中放子view的时候都需要判断当前子view是否是最后一个;如果是最后一个子 view了,那么久需要将当前line存储, * * 5.依次摆放lineList中每个line中保存的子view: a.遍历lineList,取出当前line; * b.遍历当前line,取出当前childView; * c.如果line是第一行:对childView进行摆放如下:如果childView是第一个,则left是getPaddingLeft, * top是getPaddingTop;right是left+自身的宽,bottom是top+自身的高; * 如果childView不是第一个,则取出前一个子view(记录为lastChild);则当前childView的left是 * lastChild的right+horizontalSpacing;top和lastChild一样,right是自身的left+自身的宽; * bottom是和lastChild一样; * d.如果当前line是第二行,则子view摆放的top需要在getPaddingTop的基础上+line的高度+verticalSpacing; * e.最后,由于每行的子view摆放出来之后会有剩余的空间,所以获取剩余的空间,然后除以 * 子view的数量,得到每个子view应该重新分配的值,对他们的宽重新赋值,并且这个赋值的 操作是在摆放子view之前,因为摆放的时候用到了他们的宽度 */
public class FlowLayout extends ViewGroup {
/** * 默认的间距 */
private int DEFAULT_SPACING = 25;
/** * 子view水平方向的间距 */
private int horizontalSpacing = 40;
/** * 每行之间的垂直间距 */
private int verticalSpacing = 60;
/** * 用来保存每一行line */
private ArrayList<Line> lineList = new ArrayList<Line>();
/** * 有多少行 */
int lineNumber = 0;
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayout(Context context) {
super(context);
}
/** * 分行和测量FlowLayout的宽高 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
lineList.clear();// 开始分行前先清空之前的list
int width = MeasureSpec.getSize(widthMeasureSpec);// 获得控件的宽度,是包含paddingLeft和paddingRight
// 这个宽才是我们需要比较用的宽
int noPaddingWidth = width - getPaddingLeft() - getPaddingRight();// 获取除去padding后的width
int height = MeasureSpec.getSize(0);// 获取控件的高度
Line line = null;// 声明为局部变量
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);// 获得当前子view
// 1.先测量子view,是为了保证一定能够获取到宽高
childView.measure(0, 0);// 系统会按照自己的规则去测量
if (line == null) {
line = new Line();
}
// 2.如果当前line还没有子view,则直接放入,不用判断,因为要保证每行至少有一个view
if (line.getViewList().size() == 0) {
line.addView(childView);
} else {
// 当前line中有子view
if (line.getWidth() + horizontalSpacing + childView.getMeasuredWidth() > noPaddingWidth) {
// 表示需要换行,先存放之前的line,然后在重新new Line
lineList.add(line);
line = new Line();
line.addView(childView);
// 如果当前childView是最后一个子view,则需要保存line
if (i == (getChildCount() - 1)) {
lineList.add(line);
}
} else {
// 不需要换行,直接添加到当前line
line.addView(childView);
// 如果当前childView是最后一个子view,则需要保存line
if (i == (getChildCount() - 1)) {
lineList.add(line);
}
}
}
}
line = null;
lineNumber = lineList.size();
//TODO 如果需要只显示2行,加上这个判断。在onLayout 方法中,将2个TODO定位的代码放开即可
// if (lineNumber > 2) {
// lineNumber = 2;
// }
// 计算当前FlowLayout的总高度
for (int i = 0; i < lineNumber; i++) {
height += lineList.get(i).getHeight();
}
if (lineList.size() > 0) {
height += verticalSpacing * (lineNumber - 1) + getPaddingTop() + getPaddingBottom();
// 告诉系统我要申请这么多的宽高
setMeasuredDimension(width, height);
}
}
/** * 摆放所有line中的子view */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
for (int i = 0; i < lineList.size(); i++) {
//TODO
// if (i < 2) {
Line line = lineList.get(i);// 获取当前的line
ArrayList<View> viewList = line.getViewList();// 获取当前line的子view集合
// 每行的top是不断增加的
if (i != 0) {
Line preLine = lineList.get(i - 1);// 获取上一行
paddingTop += preLine.getHeight() + verticalSpacing;
}
// 获取每行的留白
float remainSpace = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - line.getWidth();
// 计算出每个子view平分到的留白
float perSpace = remainSpace / viewList.size();
for (int j = 0; j < viewList.size(); j++) {
View childView = viewList.get(j);
// 将每个子view得到的留白重新添加到自己的宽度,然后重新测量
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (childView.getMeasuredWidth() + perSpace),
MeasureSpec.EXACTLY);
childView.measure(widthMeasureSpec, 0);
if (j == 0) {
// 如果是第一个子view,则靠左边摆放
childView.layout(paddingLeft, paddingTop, paddingLeft + childView.getMeasuredWidth(),
paddingTop + childView.getMeasuredHeight());
} else {
// 后面子view,总是比前一个要多个horizontalSpacing
View preView = viewList.get(j - 1);// 获取前一个子view
int left = preView.getRight() + horizontalSpacing;// 前一个view的right+horizontalSpacing
childView.layout(left, preView.getTop(), left + childView.getMeasuredWidth(),
preView.getBottom());
}
}
//TODO
// }
}
}
public int getHorizontalSpacing() {
return horizontalSpacing;
}
public void setHorizontalSpacing(int horizontalSpacing) {
this.horizontalSpacing = horizontalSpacing;
}
/** * 用来保存每行的子view,代表一行 * * @author Administrator * */
class Line {
ArrayList<View> viewList;// 用于记录行内所有的子view
private int width;// 表示所有子view的宽加上中间的horizontalSpacing
private int height;// 记录自身的高度
public Line() {
viewList = new ArrayList<View>();
}
/** * 保存子view到viewList中 */
public void addView(View view) {
if (!viewList.contains(view)) {
viewList.add(view);
}
// 更新width和height
if (viewList.size() == 1) {
// 只有1个子veiw,则line的width就是当前子view的宽
width = view.getMeasuredWidth();
} else {
// 有多个子veiw的时候,除了加上当前view的宽,还要加上horizontalSpacing
width += view.getMeasuredWidth() + getHorizontalSpacing();
}
// 总是取子veiw中最高的作为自己高度
height = Math.max(height, view.getMeasuredHeight());
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public ArrayList<View> getViewList() {
return viewList;
}
}
}
2、布局文件:
<?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:background="#f8f8f8" android:orientation="vertical">
<ScrollView android:layout_width="match_parent" android:layout_height="match_parent">
<LinearLayout android:id="@+id/init_interface" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<com.chen.demo.FlowLayout android:id="@+id/flowLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffffff">
</com.chen.demo.FlowLayout>
<TextView android:id="@+id/show_tv" android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
</LinearLayout>
注:这里以第一种情况讲解,为了能滑动看到超出屏幕高度的内容,需要加上ScrollView,另外,可以无视TextView。它是为了第二种情况,后面加个东西看效果的
3、代码使用:
3、1:先上工具类
package com.chen.demo;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.widget.Toast;
import java.util.Random;
public class Utils {
private static Toast toast;
/** * 单例吐司,不用等上个吐司消失,直接显示 */
public static void showToast(Context context, String msg) {
if (toast == null) {
toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
}
toast.setText(msg);
toast.show();
}
/** * 获取一个随机颜色 * @return */
public static int getRandomColor(){
Random random = new Random();
//为了保证混合生成的颜色不至于太暗和太亮,所以对rgb的值进行限定
int red = random.nextInt(150)+50;//50-200
int green = random.nextInt(150)+50;
int blue = random.nextInt(150)+50;
//使用rgb混合出一种新的颜色
return Color.rgb(red, green, blue);
}
/** * 根据颜色生成drawable * @param rgb * @return */
public static GradientDrawable generateDrawable(int rgb){
GradientDrawable drawable = new GradientDrawable();
drawable.setShape(GradientDrawable.RECTANGLE);
drawable.setColor(rgb);//设置颜色
drawable.setCornerRadius(20);
return drawable;
}
/** * 生成selector * @param pressed * @param normal * @return */
public static StateListDrawable generateSelector(Drawable pressed,Drawable normal){
StateListDrawable stateListDrawable = new StateListDrawable();
stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressed);
stateListDrawable.addState(new int[]{}, normal);
return stateListDrawable;
}
}
3、2:具体使用
package com.chen.demo;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Random;
public class MainActivity extends Activity {
private FlowLayout flowLayout;
private TextView show_tv;
private ArrayList<String> list;
private int x=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
flowLayout= (FlowLayout) findViewById(R.id.flowLayout);
flowLayout.setPadding(12, 12, 12, 12);
//第一种情况,可以无视TextView
show_tv= (TextView) findViewById(R.id.show_tv);
show_tv.setText("Android");
show_tv.setBackgroundColor(Utils.getRandomColor());
show_tv.setGravity(Gravity.CENTER);
list=new ArrayList<String>();
Random random = new Random();
/** * 生成模拟数据。 */
for(int i=0;i<500;i++){
x=random.nextInt(500);
list.add(x+"");
}
/** * 放数据 */
if (list.size()>0){
for (int i=0;i<list.size();i++){
//创建textView,并设置属性
final TextView textView = new TextView(this);
textView.setPadding(10, 5, 10, 5);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 50);
textView.setGravity(Gravity.CENTER);
textView.setTextColor(Color.WHITE);
//textView点击前的普通状态
Drawable normal = Utils.generateDrawable(Utils.getRandomColor());
//textView被点击时的状态
Drawable pressed = Utils.generateDrawable(Color.GRAY);
//给textView设置状态选择器
textView.setBackgroundDrawable(Utils.generateSelector(pressed, normal));
//给textView设置内容
textView.setText(list.get(i));
//给textView设置点击事件
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Utils.showToast(MainActivity.this,textView.getText().toString());
}
});
//把textView放到flowLayout中
flowLayout.addView(textView);
}
}
}
}