Android 自定义View实现多行RadioGroup单选(多选)

我们都知道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
    


四、部分方法说明

  • append(String str) 附加一个child;
  • insert(int position, String str) 往指定位置插入child;
  • getCheckedValues()|getCheckedItems() 获取选中项;
  • remove(int position) 删除指定位置的child;
  • setItemChecked(int position) 选中指定位置的child;

五、 Demo下载链接

你可能感兴趣的:(Android)