我们都知道RadioGroup可以实现选择框,但它有一个局限性,由于它是继承自LinearLayout的,所以只能有一个方向,横向或者纵向;
好在我们可以自定义View来实现多行的一个RadioGroup(我把它命令为MultiLineRadioGroup);
在贴出代码之前,先来分析一下思路:
1、首先自定义一个View继承自ViewGroup,并且重写onMeasure方法和onLayout方法,分别用于测量Child尺寸和在ViewGroup中放置Child;
2、指定单个child元素,通过自定义属性的方式,由于要实现选择,即child是checkable的,我这里选择使用CheckBox作为child,使用的时候先在layout下指定一个xml文件并且设定它的根节点为CheckBox,然后把这个layout配置到MultiLineRadioGroup节点的child节点对应的属性中;
3、onMeasure方法中,我们只需要遍历ViewGroup的child并且调用measureChild方法对child进行测量;
4、onLayout方法中,我们根据child的尺寸来对child进行放置,具体来讲就是分别定义两个变量来记录上一个child的左上角Y坐标和右下角X坐标,并且根据当前要layout的child的尺寸进行是否需要换行的判断,如果当前要layout的child的宽度加上前一个View的右下角X坐标值大于当前MultiLineRadioGroup的宽度,则换行;摆放一个child完成之后需要对两个变量进行更新;
5、在onLayout的基础上,我们加入了child的水平间距和垂直间距的设置,通过自定义属性的方式;
6、在上面的基础上,对child进行统一化的管理,管理它的选择状态,以及添加、删除、选中一个child等常用方法;
再来预览一下程序界面效果图;
一、MultiLineRadioGroup.java
// org.ccflying.MultiLineRadioGroup
public class MultiLineRadioGroup extends ViewGroup implements OnClickListener {
private int mX, mY;
private List viewList;
private int childMarginHorizontal = 0;
private int childMarginVertical = 0;
private int childResId = 0;
private int childCount = 0;
private int childValuesId = 0;
private boolean singleChoice = false;
private int mLastCheckedPosition = -1;
private OnCheckedChangedListener listener;
private List childValues;
private boolean forceLayout;
public MultiLineRadioGroup(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
viewList = new ArrayList();
childValues = new ArrayList();
TypedArray arr = context.obtainStyledAttributes(attrs,
R.styleable.MultiLineRadioGroup);
childMarginHorizontal = arr.getDimensionPixelSize(
R.styleable.MultiLineRadioGroup_child_margin_horizontal, 15);
childMarginVertical = arr.getDimensionPixelSize(
R.styleable.MultiLineRadioGroup_child_margin_vertical, 5);
childResId = arr.getResourceId(
R.styleable.MultiLineRadioGroup_child_layout, 0);
childCount = arr.getInt(R.styleable.MultiLineRadioGroup_child_count, 0);
singleChoice = arr.getBoolean(
R.styleable.MultiLineRadioGroup_single_choice, true);
childValuesId = arr.getResourceId(
R.styleable.MultiLineRadioGroup_child_values, 0);
if (childResId == 0) {
throw new RuntimeException(
"The attr 'child_layout' must be specified!");
}
if (childValuesId != 0) {
String[] childValues_ = getResources()
.getStringArray(childValuesId);
for (String str : childValues_) {
childValues.add(str);
}
}
if (childCount > 0) {
boolean hasValues = childValues != null;
for (int i = 0; i < childCount; i++) {
View v = LayoutInflater.from(context).inflate(childResId, null);
if (!(v instanceof CheckBox)) {
throw new RuntimeException(
"The attr child_layout's root must be a CheckBox!");
}
CheckBox cb = (CheckBox) v;
viewList.add(cb);
addView(cb);
if (hasValues && i < childValues.size()) {
cb.setText(childValues.get(i));
} else {
childValues.add(cb.getText().toString());
}
cb.setTag(i);
cb.setOnClickListener(this);
}
} else {
Log.d("tag", "childCount is 0");
}
arr.recycle();
}
public MultiLineRadioGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MultiLineRadioGroup(Context context) {
this(context, null, 0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
childCount = getChildCount();
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
measureChild(v, widthMeasureSpec, heightMeasureSpec);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!changed && !forceLayout) {
Log.d("tag", "onLayout:unChanged");
return;
}
mX = mY = 0;
childCount = getChildCount();
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
if (v.getMeasuredWidth() + childMarginHorizontal * 2 + mX > getWidth()) {
mY++;
mX = 0;
}
int startX = mX + childMarginHorizontal;
int startY = mY * v.getMeasuredHeight() + (mY + 1)
* childMarginVertical;
v.layout(startX, startY, startX + v.getMeasuredWidth(), startY
+ v.getMeasuredHeight());
mX += v.getMeasuredWidth() + childMarginHorizontal * 2;
}
}
forceLayout = false;
}
@Override
public void onClick(View v) {
if (singleChoice) {
try {
int i = (Integer) v.getTag();
if (mLastCheckedPosition == i) {
return;
}
if (mLastCheckedPosition >= 0
&& mLastCheckedPosition < viewList.size()) {
viewList.get(mLastCheckedPosition).setChecked(false);
}
mLastCheckedPosition = i;
if (listener != null) {
listener.onItemChecked(this, i);
}
} catch (Exception e) {
}
} else { // multiChoice
}
}
public void setOnCheckChangedListener(OnCheckedChangedListener l) {
this.listener = l;
}
public boolean setItemChecked(int position) {
if (position >= 0 && position < viewList.size()) {
if (singleChoice) {
if (position == mLastCheckedPosition) {
return true;
}
if (mLastCheckedPosition >= 0
&& mLastCheckedPosition < viewList.size()) {
viewList.get(mLastCheckedPosition).setChecked(false);
}
}
viewList.get(position).setChecked(true);
return true;
}
return false;
}
public boolean isSingleChoice() {
return singleChoice;
}
public void setChoiceMode(boolean isSingle) {
this.singleChoice = isSingle;
if (singleChoice) {
if (getCheckedValues().size() > 1) {
clearChecked();
}
}
}
public int[] getCheckedItems() {
if (singleChoice && mLastCheckedPosition >= 0
&& mLastCheckedPosition < viewList.size()) {
return new int[] { mLastCheckedPosition };
}
SparseIntArray arr = new SparseIntArray();
for (int i = 0; i < viewList.size(); i++) {
if (viewList.get(i).isChecked()) {
arr.put(i, i);
}
}
if (arr.size() != 0) {
int[] r = new int[arr.size()];
for (int i = 0; i < arr.size(); i++) {
r[i] = arr.keyAt(i);
}
return r;
}
return null;
}
public List getCheckedValues() {
List list = new ArrayList();
if (singleChoice && mLastCheckedPosition >= 0
&& mLastCheckedPosition < viewList.size()) {
list.add(viewList.get(mLastCheckedPosition).getText().toString());
return list;
}
for (int i = 0; i < viewList.size(); i++) {
if (viewList.get(i).isChecked()) {
list.add(viewList.get(i).getText().toString());
}
}
return list;
}
public int append(String str) {
View v = LayoutInflater.from(getContext()).inflate(childResId, null);
if (!(v instanceof CheckBox)) {
throw new RuntimeException(
"The attr child_layout's root must be a CheckBox!");
}
CheckBox cb = (CheckBox) v;
cb.setText(str);
cb.setTag(childCount);
cb.setOnClickListener(this);
viewList.add(cb);
addView(cb);
childValues.add(str);
childCount++;
forceLayout = true;
postInvalidate();
return childCount - 1;
}
public void addAll(List list) {
if (list != null && list.size() > 0) {
for (String str : list) {
append(str);
}
}
}
public boolean remove(int position) {
if (position >= 0 && position < viewList.size()) {
CheckBox cb = viewList.remove(position);
removeView(cb);
childValues.remove(cb.getText().toString());
childCount--;
forceLayout = true;
if (position <= mLastCheckedPosition) { // before LastCheck
if (mLastCheckedPosition == position) {
mLastCheckedPosition = -1;
} else {
mLastCheckedPosition--;
}
}
for (int i = position; i < viewList.size(); i++) {
viewList.get(i).setTag(i);
}
postInvalidate();
return true;
}
return false;
}
public boolean insert(int position, String str) {
if (position < 0 || position > viewList.size()) {
return false;
}
View v = LayoutInflater.from(getContext()).inflate(childResId, null);
if (!(v instanceof CheckBox)) {
throw new RuntimeException(
"The attr child_layout's root must be a CheckBox!");
}
CheckBox cb = (CheckBox) v;
cb.setText(str);
cb.setTag(position);
cb.setOnClickListener(this);
viewList.add(position, cb);
addView(cb, position);
childValues.add(position, str);
childCount++;
forceLayout = true;
if (position <= mLastCheckedPosition) { // before LastCheck
mLastCheckedPosition++;
}
for (int i = position; i < viewList.size(); i++) {
viewList.get(i).setTag(i);
}
postInvalidate();
return true;
}
public void clearChecked() {
if (singleChoice) {
if (mLastCheckedPosition >= 0
&& mLastCheckedPosition < viewList.size()) {
viewList.get(mLastCheckedPosition).setChecked(false);
mLastCheckedPosition = -1;
return;
}
}
for (CheckBox cb : viewList) {
if (cb.isChecked()) {
cb.setChecked(false);
}
}
}
public interface OnCheckedChangedListener {
public void onItemChecked(MultiLineRadioGroup group, int position);
}
}
MultiLineRadioGroup里面的方法都不得太难,不再细说;
二、自定义属性attrs.xml
在这里我定义了6个自定义属性,其中child_layout是必须指定的,并且child_layout对应的layout文件的要节点必须是CheckBox,我们可以在这里对child进行样式的统一设置;其它的几个属性分别是水平间距、垂直间距、child元素个数,child(CheckBox)元素的Text数组,单选/多选(默认是单选);
三、MultiLineRadioGroup使用
child.xml
childvalues
- child1
- child2
- childchild3
- child4
- childchild5
- childchildchild6
- child7
- child8