本文demo下载地址:https://github.com/JenniferHuLL/TabLayoutDemo-master
运行效果图
单选:
多选:
背景
由于公司近期要求做的功能需要实现一个多行的单选控件所以想到了RadioGroup,经过在网上的搜索之后发现只有这个demo比较适合我的需求,但是也有很多不足,比如单个item的宽度没有固定这在体验上很不好,还有高度没有统一等等,所以我决定改造一下这个demo为我所用。
实现目标:
1、支持多选、单选。
2、支持内容背景自定义。
3、item统一的宽高。
实现思路
1、通过自定义属性获取数据:
<declare-styleable name="MultiLineRadioGroup">
<attr name="child_margin_horizontal" format="dimension" />
<attr name="child_margin_vertical" format="dimension" />
<attr name="child_layout" format="integer" />
<attr name="child_count" format="integer" />
<attr name="child_values" format="integer" />
<attr name="single_choice" format="boolean" />
<attr name="child_gravity" format="integer" />
<attr name="mlrg_line_num" format="integer" />
</declare-styleable>
上面的几个自定义属性分别表示:
- child水平间距
- child上下间距
- child对应的layout布局文件
- 初始元素总个数
- 初始元素值列表
- 选择模式(true 单选 | false 多选)
- child对齐方式
- 一行的个数(默认4个)
通过以上的自定义属性获取到item总数(child_count,姑且称为C)以及一行的个数(mlrg_line_num,姑且称为M),然后算出单个item的宽度W;具体代码如下:
flagY:W
childCount :child_count
childNumLine:mlrg_line_num
flagY = childCount % childNumLine == 0 ? (childCount / childNumLine) :
(childCount / childNumLine + 1);
按照以上思路目标1已经实现目标3实现了固定item宽度,但是高度和背景自定义怎么实现呢?这就得靠属性“child_layout”来实现。首先声明,由于为了实现多选故使用了CheckBox来作为属性“child_layout”的基本控件,意思就是如果要自定义item的点击效果也就是背景必须使用CheckBox,例如例子中的“child_house_rental_gd.xml”布局。到了这一步了相信很容易就能想到目标2的内容背景自定义的实现方法了,那就是设置“child_house_rental_gd.xml”布局的背景属性,至于高度那就直接定义“child_house_rental_gd.xml”布局的“android:lines”属性即可。好了,说到这里整个的思路就很清晰了;下面贴一些关键代码帮助理解,具体的可以下载demo研究。
demo介绍
1、单选是默认的子布局(”child_layout”)。
2、多选是自定义背景(”child_layout”)
关键代码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
childCount = getChildCount();
int flagX = 0, flagY, sheight = 0;
flagY = childCount % childNumLine == 0 ? (childCount / childNumLine) :
(childCount / childNumLine + 1);
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
measureChild(v, widthMeasureSpec, heightMeasureSpec);
int w = v.getMeasuredWidth() + childMarginHorizontal * 2
+ flagX + getPaddingLeft() + getPaddingRight();
if (w > getMeasuredWidth()) {
flagX = 0;
}
sheight = v.getMeasuredHeight();
flagX += v.getMeasuredWidth() + childMarginHorizontal * 2;
}
rowNumber = flagY;
}
int height = flagY * (sheight + childMarginVertical)
+ childMarginVertical + getPaddingBottom() + getPaddingTop();
setMeasuredDimension(getMeasuredWidth(), height);
}
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed && !forceLayout) {
Log.d("tag", "onLayout:unChanged");
return;
}
childCount = getChildCount();
int[] sX = new int[rowNumber + 1];
if (childCount > 0) {
if (gravity != LEFT) {
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
int w = v.getMeasuredWidth() + childMarginHorizontal * 2
+ mX + getPaddingLeft() + getPaddingRight();
if (w > getWidth()) {
if (gravity == CENTER) {
sX[mY] = (getWidth() - mX) / 2;
} else { // right
sX[mY] = (getWidth() - mX);
}
mY++;
mX = 0;
}
mX += v.getMeasuredWidth() + childMarginHorizontal * 2;
if (i == childCount - 1) {
if (gravity == CENTER) {
sX[mY] = (getWidth() - mX) / 2;
} else { // right
sX[mY] = (getWidth() - mX);
}
}
}
mX = mY = 0;
}
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
int w = v.getMeasuredWidth() + childMarginHorizontal * 2 + mX
+ getPaddingLeft() + getPaddingRight();
if (w > getWidth()) {
mY++;
mX = 0;
}
int startX = mX + childMarginHorizontal + getPaddingLeft()
+ sX[mY];
int startY = mY * v.getMeasuredHeight() + (mY + 1)
* childMarginVertical;
v.layout(startX, startY, startX + v.getMeasuredWidth(), startY
+ v.getMeasuredHeight());
mX += v.getMeasuredWidth() + childMarginHorizontal * 2;
}
}
mX = mY = 0;
forceLayout = false;
}