效果图
![宽度填满效果图](https://i-blog.csdnimg.cn/blog_migrate/dfc3cc744ad008598abc2415cd1eae7b.jpeg)
![宽度不填满效果图](https://i-blog.csdnimg.cn/blog_migrate/cdcdd573724cda29ad088bcc5a372245.jpeg)
实现上图布局,关键是重写ViewGroup的 onMeasure 和 onLayout方法
- onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法:作用是确定ViewGroup的宽高。
- onLayout(boolean changed, int left, int top, int right, int bottom) 方法:作用是设置ViewGroup里的View的位置
实现思路
- 在onMeasure(int w, int h)方法里,获取ViewGroup的可用宽度,使用getChildCount()获取view的数量,循环获取view的对象并使用measureChild(child, widthMeasureSpec, heightMeasureSpec)测量view的宽高,通过getMeasuredWidth()和getMeasuredHeight()获取view的测量宽高,当view的测量宽度累加大于ViewGroup的可用宽度时,就表示ViewGroup需要添加一行来放置多出来的view
- 在onLayout(boolean changed, int left, int top, int right, int bottom)方法里,根据onMeasure(int w, int h)方法处理后获得的总行数(此时,一行有多少个view和一行的高度都已知),通过view.layout(left,top,right,bottom)来放置view的位置
实现的代码
package com.wudengwei.tagflowlayout;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* Created by wudengwei
* on 2018/12/4
*/
public class TagFlowLayout extends ViewGroup {
/*保存所有的view*/
private List<Line> mLines;
/*正在使用的行*/
private Line mCurrentLine;
/*tag之间的间隔*/
private int tagSpace;
/*line之间的间隔*/
private int lineSpace;
/*是否让所有的line的使用宽度等于最大宽度*/
private boolean fillAllLineWidth = false;
public void setFillAllLineWidth(boolean fillAllLineWidth) {
this.fillAllLineWidth = fillAllLineWidth;
}
/*tag点击事件回调*/
private OnItemClickListener mOnItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
}
public TagFlowLayout(Context context) {
this(context,null);
}
public TagFlowLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public TagFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLines = new ArrayList<>();
tagSpace = dp2px(context,10f);
lineSpace = dp2px(context,10f);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.e("onMeasure",""+widthMeasureSpec);
/*清空*/
mLines.clear();
mCurrentLine = null;
/*总宽度*/
int totalWidth = MeasureSpec.getSize(widthMeasureSpec);
/*最大可以使用的宽度*/
int maxWidth = totalWidth - getPaddingLeft() - getPaddingRight();
for (int i=0;i<getChildCount();i++) {
View child = getChildAt(i);
/*测量孩子的宽高*/
measureChild(child,widthMeasureSpec,heightMeasureSpec);
/*无论如何,必须存在一个当前使用的line*/
if (mCurrentLine == null) {
mCurrentLine = new Line(maxWidth,tagSpace);
mCurrentLine.fillWidth = fillAllLineWidth;
mCurrentLine.addView(child);
mLines.add(mCurrentLine);
} else {
/*当前的line可以添加view就添加,否则再创建一个line(下一行)*/
if (mCurrentLine.canAddView(child)) {
mCurrentLine.addView(child);
} else {
mCurrentLine = new Line(maxWidth,tagSpace);
mCurrentLine.fillWidth = fillAllLineWidth;
mCurrentLine.addView(child);
mLines.add(mCurrentLine);
}
}
}
/*总高度*/
int totalHeight = getPaddingTop() + getPaddingBottom();
for (Line line : mLines) {
totalHeight = totalHeight + line.height;
}
totalHeight = totalHeight + (mLines.size()-1) * lineSpace;
/*设置viewGroup的大小*/
setMeasuredDimension(totalWidth,totalHeight);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Log.e("onLayout",""+changed);
int marginTop = getPaddingTop();
int marginLeft = getPaddingLeft();
/*设置line中的所有view的位置*/
for (int i=0;i<mLines.size();i++){
Line line = mLines.get(i);
/*最后一行不填满宽度*/
if (i == mLines.size()-1) {
line.fillWidth = false;
}
line.layout(marginLeft,marginTop);
marginTop += lineSpace + line.height;
}
/*添加点击事件*/
int index = 0;
for (Line line : mLines) {
for (int i=0;i<line.viewList.size();i++) {
final int position = i + index;
View view = line.viewList.get(i);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mOnItemClickListener != null)
mOnItemClickListener.click(view,position);
}
});
}
index = index + line.viewList.size();
}
}
/**
* 保存一行的view
*/
private class Line {
private List<View> viewList;
private int usedWidth;//已经使用的宽度
private int maxWidth;//最大可以使用的宽度
private int viewSpace;//view之间的间隔
private int height;//高度
private boolean fillWidth = false;//是否设置所有view的宽度和≈maxWidth
public Line(int maxWidth, int viewSpace) {
this.maxWidth = maxWidth;
this.viewSpace = viewSpace;
viewList = new ArrayList<>();
}
/*在可以添加view的前提下使用*/
public void addView(View view) {
int viewWidth = view.getMeasuredWidth();
int viewHeight = view.getMeasuredHeight();
if (viewList.size() == 0) {
/*如果view的宽度大于最大可用宽度,强制设置为最大宽度*/
usedWidth = viewWidth > maxWidth ? maxWidth : viewWidth;
height = viewHeight;
} else {
usedWidth = usedWidth + viewSpace + viewWidth;
height = viewHeight > height ? viewHeight : height;
}
viewList.add(view);
}
/*判断viewList是否可以添加view*/
public boolean canAddView(View view) {
int viewWidth = view.getMeasuredWidth();
if (viewList.size() == 0)
return true;
/*已经使用的宽度加上viewWidth后,小于或等于表示可以添加*/
return viewWidth + viewSpace + usedWidth <= maxWidth;
}
public void layout(int marginLeft, int marginTop) {
/*avgWidth是为了让line的view铺满maxWidth,右边不会有空白*/
int avgWidth = (int) ((maxWidth - usedWidth) * 1f / viewList.size());
//计算控件的上下左右的位置
for(View view : viewList) {
/*获取view的测量宽度*/
int viewWidth = view.getMeasuredWidth();
int viewHeight = view.getMeasuredHeight();
/*重新设置view的宽度,让所有view的宽度和≈maxWidth*/
if(avgWidth > 0 && fillWidth){
/*按照给定的宽高设置view的大小*/
int specWidth = MeasureSpec.makeMeasureSpec(viewWidth + avgWidth,MeasureSpec.EXACTLY);
int specHeight = MeasureSpec.makeMeasureSpec(viewHeight,MeasureSpec.EXACTLY);
view.measure(specWidth,specHeight);
// 重新获取宽度和高度
viewWidth = view.getMeasuredWidth();
viewHeight = view.getMeasuredHeight();
}
/*extraTop为了让view垂直居中而取的参数(因为viewHeight的大小不固定)*/
int extraTop = (int) ((height - viewHeight)*0.5f);
int left = marginLeft;
int top = marginTop + extraTop;
int right = left + viewWidth;
int bottom = top + viewHeight;
//摆放每一个孩子的位置
view.layout(left,top,right,bottom);
marginLeft += viewWidth + viewSpace;
}
}
}
public interface OnItemClickListener {
void click(View view, int position);
}
/*dp转px*/
private int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}
}
参考:https://blog.csdn.net/wk843620202/article/details/51673696