遇到多项选择的情况,我们可以使用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)),这里就不再赘述。
最后贴一张效果图: