遇到多项选择的情况,我们可以使用RadioGroup,但是它只能处理单行或者单列的情况,如果标签很多就需要滑动屏幕了。考虑到显示的简洁性,我们也可以考虑自定义一个换行控件。这里就记录一下我实现的换行控件WordWrapperView。
一、大致思路:
WordWrapperView扩展自ViewGroup,根据内容标签测量尺寸,确定内容标签的位置。
二、实现:
WordWrapperView类代码:
/**
* 自动换行的空间,只支持自动换行<br/>
* 1.没有考虑单个标签过长的情况<br/>
* 2.不支持滚动,即标签过多时不支持上下滑动<br/>
* 3.默认标签的高度都是一样的<br/>
* Created by hsji on 16/1/9.
*/
public class WordWrapperView extends ViewGroup {
private static final String TAG = WordWrapperView.class.getSimpleName();
private static final int DEFAULT_HORIZONTAL_SPACING = 10;
private static final int DEFAULT_VERTICAL_SPACING = 10;
/**
* 水平间距
*/
private int mHorizontalSpacing;
/**
* 垂直间距
*/
private int mVerticalSpacing;
public WordWrapperView(Context context) {
this(context, null);
}
public WordWrapperView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WordWrapperView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//通过 TypedArray 获取自定义属性
TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.WordWrapperView);
//获取自定义属性的个数
int N = typedArray.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.WordWrapperView_horizontal_spacing:
//通过自定义属性拿到水平间距
mHorizontalSpacing = typedArray.getDimensionPixelSize(attr, DEFAULT_HORIZONTAL_SPACING);
break;
case R.styleable.WordWrapperView_vertical_spacing:
//通过自定义属性拿到垂直间距
mVerticalSpacing = typedArray.getDimensionPixelSize(attr, DEFAULT_VERTICAL_SPACING);
break;
}
}
//使用完成之后记得回收
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
//必须先测量出宽度,之后才能测量高度
int width = measureWidth(widthMeasureSpec);
//根据测量出的宽度,测量高度
int height = measureHeight(heightMeasureSpec, width);
setMeasuredDimension(width, height);
}
/**
* 计算所有标签排成一行所需要的宽度,再根据mode处理
*
* @param widthMeasureSpec
* @return
*/
private int measureWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int desired = getPaddingLeft();
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
if (desired == getPaddingLeft()) {
desired += childWidth;
} else {
desired += mHorizontalSpacing + childWidth;
}
}
desired += getPaddingRight();
if (mode == MeasureSpec.EXACTLY) {
return size;
} else {
if (mode == MeasureSpec.AT_MOST) {
return Math.min(desired, size);
} else {
return desired;
}
}
}
/**
* 先计算出需要排多少行,根据行数算出高度,再根据mode处理
*
* @param heightMeasureSpec
* @param width
* @return
*/
private int measureHeight(int heightMeasureSpec, int width) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int counter = 0;//rows
int totalWidth = getPaddingLeft();
int count = getChildCount();
int childHeight = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
//第一行第一列的标签
if (totalWidth == getPaddingLeft()) {
totalWidth += childWidth;
childHeight = child.getMeasuredHeight();
} else {
//第一列但非第一行的标签
if (totalWidth + mHorizontalSpacing + childWidth <= width - getPaddingRight()) {
totalWidth += mHorizontalSpacing + childWidth;
} else {//非第一列的标签
counter++;
totalWidth = getPaddingLeft() + childWidth;
}
}
}
int desired = getPaddingTop() + childHeight + (mVerticalSpacing + childHeight) * counter + getPaddingBottom();
if (mode == MeasureSpec.EXACTLY) {
return size;
} else {
if (mode == MeasureSpec.AT_MOST) {
return Math.min(desired, size);
} else {
return desired;
}
}
}
/**
* 采用两个变量totalWidth和totalHeight来记录坐标,考虑的情况和measureHeight类似
*
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int count = getChildCount();
int totalWidth = getPaddingLeft();
int totalHeight = getPaddingTop();
int childHeight = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
childHeight = child.getMeasuredHeight();
if (totalWidth == getPaddingLeft()) {
child.layout(totalWidth,
totalHeight,
totalWidth + child.getMeasuredWidth(),
totalHeight + child.getMeasuredHeight());
totalWidth += child.getMeasuredWidth();
} else {
if (totalWidth + mHorizontalSpacing + child.getMeasuredWidth() <= r - l - getPaddingRight()) {
child.layout(totalWidth + mHorizontalSpacing,
totalHeight,
totalWidth + mHorizontalSpacing + child.getMeasuredWidth(),
totalHeight + child.getMeasuredHeight());
totalWidth += mHorizontalSpacing + child.getMeasuredWidth();
} else {
totalWidth = getPaddingLeft();
totalHeight += mVerticalSpacing + childHeight;
child.layout(totalWidth, totalHeight, totalWidth + child.getMeasuredWidth(), totalHeight + child.getMeasuredHeight());
totalWidth += child.getMeasuredWidth();
}
}
}
}
}
}
自定义属性:
<declare-styleable name="WordWrapperView">
<attr name="horizontal_spacing" format="dimension"></attr>
<attr name="vertical_spacing" format="dimension"></attr>
</declare-styleable>
三、使用
<com.hsji.testapp.widget.WordWrapperView
android:id="@+id/wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
app:horizontal_spacing="10dp"
app:vertical_spacing="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_round_corner"
android:padding="8dp"
android:text="标签1"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_round_corner"
android:padding="8dp"
android:text="标签2"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_round_corner"
android:padding="8dp"
android:text="标签3"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_round_corner"
android:padding="8dp"
android:text="标签4"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_round_corner"
android:padding="8dp"
android:text="标签5"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_round_corner"
android:padding="8dp"
android:text="标签6"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_round_corner"
android:padding="8dp"
android:text="标签7"
android:textColor="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_round_corner"
android:padding="8dp"
android:text="标签8"
android:textColor="@android:color/white" />
</com.hsji.testapp.widget.WordWrapperView>
以上是直接在布局文件里面设置标签,也可以通过addView的方式往WordWrapperView里面添加标签。
另外可以为内容标签设置点击事件(tv.setOnClickListener(listener)),这里就不再赘述。
最后贴一张效果图: