Android中,标签流布局就是按照从左到右,从上到下的布局方式显示标签,当位置不够标签长度的时候,就换行显示。实现的原理也比较简单,就是先设计行管理器,其中行管理器主要负责获取行高以及添加子子View进行里。之后在ViewGroup中的onMeasure方法中对行进行计算得出总的高度,重新测量设置ViewGroup的宽高。
这里主要现在values目录下创建attr文件,进行自定义View的属性声明。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlowTagLayout">
<attr name="horizontalSpacing" format="dimension" />
<attr name="verticalSpacing" format="dimension" />
</declare-styleable>
</resources>
再实现一个标签的样式,在FlowTagLayout种的setFlowTagLayout方法中使用到,这里实现一个简单的TextView显示标签,使用圆角矩形作为背景。
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item"
android:padding="5dp"
android:background="@drawable/shape_item_bg">
</TextView>
标签背景:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="5dp" />
<stroke android:color="#e25b4f" android:width="1dp" />
<solid android:color="@android:color/transparent" />
</shape>
自定义FlowTagLayout的具体实现如下:
public class FlowTagLayout extends ViewGroup{
private int horizontalSpacing = dip2px(10); //水平方向上标签之间的间隔
private int verticalSpacing = dip2px(10); //行距
private Line mCurrLine;
private List<Line> mLines = new ArrayList<>();
private int mLineSize;
public FlowTagLayout(Context context) {
this(context, null);
}
public FlowTagLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowTagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlowTagLayout, defStyleAttr, 0);
horizontalSpacing = ta.getDimensionPixelSize(R.styleable.FlowTagLayout_horizontalSpacing, horizontalSpacing);
verticalSpacing = ta.getDimensionPixelSize(R.styleable.FlowTagLayout_verticalSpacing, verticalSpacing);
ta.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//实际可用高度
int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
int widthModel = MeasureSpec.getMode(widthMeasureSpec);
int heightModel = MeasureSpec.getMode(heightMeasureSpec);
//初始化
mLines.clear();
mLineSize = 0;
mCurrLine = new Line();
for (int i = 0; i < getChildCount(); i++){
//测量子View的高度
View childView = getChildAt(i);
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthModel == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthModel);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, heightModel == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightModel);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
mLineSize += childView.getMeasuredWidth();
if (mLineSize < width){
mCurrLine.addView(childView);
mLineSize += horizontalSpacing;
}else{ //换行
if (mCurrLine != null){
mLines.add(mCurrLine);
}
mCurrLine = new Line();
mLineSize = 0;
mCurrLine .addView(childView);
mLineSize += childView.getMeasuredWidth() + horizontalSpacing;
}
}
//加上最后一行
if (mCurrLine != null && !mLines.contains(mCurrLine)){
mLines.add(mCurrLine);
}
int totalHeight = 0;
for (int i = 0; i < mLines.size(); i++){
totalHeight += mLines.get(i).getLineHeight();
}
totalHeight += verticalSpacing * (mLines.size() - 1);
totalHeight += getPaddingBottom() + getPaddingTop();
//重测高度
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), resolveSize(totalHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
left = getPaddingLeft();
top = getPaddingTop();
for (int i = 0; i < mLines.size(); i++){
Line line = mLines.get(i);
line.layout(left, top);
top += line.getLineHeight() + verticalSpacing;
}
}
/**
* 行管理器, 管理每一行的孩子
*/
private class Line{
private List<View> lineViews = new ArrayList<>();
int maxHeight;
private void addView(View view){
lineViews.add(view);
if (maxHeight < view.getMeasuredHeight()){
maxHeight = view.getMeasuredHeight();
}
}
/**
* 指定绘制子View的位置
* @param left 左上角x轴坐标
* @param top 左上角y轴坐标
*/
private void layout(int left, int top){
int currLeft = left;
for (View view : lineViews){
view.layout(currLeft, top, currLeft + view.getMeasuredWidth(), top + view.getMeasuredHeight());
currLeft += (view.getMeasuredWidth() + horizontalSpacing);
}
}
private int getLineHeight(){
return maxHeight;
}
}
public void setFlowTagLayout(List<String> dataList, final OnItemClickListener onItemClickListener){
for (final String tag : dataList){
TextView tv = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.layout_item_tag, null);
tv.setText(tag);
this.addView(tv, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (onItemClickListener != null){
onItemClickListener.onItemClick(tag);
}
}
});
}
}
//对外开放接口
public interface OnItemClickListener{
void onItemClick(String tag);
}
private int dip2px(float dip){
float scale = getContext().getResources().getDisplayMetrics().density;
return (int)(scale * dip + 0.5);
}
}