Android 自定义RadioGroup布局

前段时间项目中需要这种效果,自定义RadioGroup布局,但是前提是要找到RadioButton。我搜集各种资料,尝试各种测试。终于有了自定义RadioGroup布局。自定RadioGroup布局,里面可以包含各种布局,RadioButton也在这里面的布局。通过递归查找方法能找到RadioButton的id,还是一组的。接下来看代码。

转载请注明出处:单击此处

欢迎下载测试demo:单击此处

一、自定义CustomNestRadioGroup类。

package com.example.customgroupdemo;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.LinearLayout;

//这个类是源码RadioGroup复制出来,增加了递归查找子控件RadioButton
public class CustomNestRadioGroup extends LinearLayout {
	// holds the checked id; the selection is empty by default
	private int mCheckedId = -1;
	// tracks children radio buttons checked state
	private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
	// when true, mOnCheckedChangeListener discards events
	private boolean mProtectFromCheckedChange = false;
	private OnCheckedChangeListener mOnCheckedChangeListener;
	private PassThroughHierarchyChangeListener mPassThroughListener;

	/**
	 * {@inheritDoc}
	 */
	public CustomNestRadioGroup(Context context) {
		super(context);
		init();
	}

	/**
	 * {@inheritDoc}
	 */
	public CustomNestRadioGroup(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	private void init() {
		mCheckedId = View.NO_ID;
//		setOrientation(VERTICAL);   //可以在这里设置线性布局方向
		mChildOnCheckedChangeListener = new CheckedStateTracker();
		mPassThroughListener = new PassThroughHierarchyChangeListener();
		super.setOnHierarchyChangeListener(mPassThroughListener);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
		// the user listener is delegated to our pass-through listener
		mPassThroughListener.mOnHierarchyChangeListener = listener;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();

		// checks the appropriate radio button as requested in the XML file
		if (mCheckedId != View.NO_ID) {
			mProtectFromCheckedChange = true;
			setCheckedStateForView(mCheckedId, true);
			mProtectFromCheckedChange = false;
			setCheckedId(mCheckedId);
		}
	}

	/** 递归查找具有选中属性的子控件 */
	private static CompoundButton findCheckedView(View child) {
		if (child instanceof CompoundButton)
			return (CompoundButton) child;
		if (child instanceof ViewGroup) {
			ViewGroup group = (ViewGroup) child;
			for (int i = 0, j = group.getChildCount(); i < j; i++) {
				CompoundButton check = findCheckedView(group.getChildAt(i));
				if (check != null)
					return check;
			}
		}
		return null;// 没有找到
	}

	@Override
	public void addView(View child, int index, ViewGroup.LayoutParams params) {
		final CompoundButton view = findCheckedView(child);
		if (view != null) {
			if (view.isChecked()) {
				mProtectFromCheckedChange = true;
				if (mCheckedId != -1) {
					setCheckedStateForView(mCheckedId, false);
				}
				mProtectFromCheckedChange = false;
				setCheckedId(view.getId());
			}
		}
		super.addView(child, index, params);
	}

	/**
	 * 

* Sets the selection to the radio button whose identifier is passed in * parameter. Using -1 as the selection identifier clears the selection; * such an operation is equivalent to invoking {@link #clearCheck()}. *

* * @param id * the unique id of the radio button to select in this group * * @see #getCheckedRadioButtonId() * @see #clearCheck() */ public void check(int id) { // don't even bother if (id != -1 && (id == mCheckedId)) { return; } if (mCheckedId != -1) { setCheckedStateForView(mCheckedId, false); } if (id != -1) { setCheckedStateForView(id, true); } setCheckedId(id); } private void setCheckedId(int id) { mCheckedId = id; if (mOnCheckedChangeListener != null) { mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId); } } private void setCheckedStateForView(int viewId, boolean checked) { View checkedView = findViewById(viewId); if (checkedView != null && checkedView instanceof CompoundButton) { ((CompoundButton) checkedView).setChecked(checked); } } /** *

* Returns the identifier of the selected radio button in this group. Upon * empty selection, the returned value is -1. *

* * @return the unique id of the selected radio button in this group * * @see #check(int) * @see #clearCheck() * * @attr ref android.R.styleable#CustomNestRadioGroup_checkedButton */ public int getCheckedRadioButtonId() { return mCheckedId; } /** *

* Clears the selection. When the selection is cleared, no radio button in * this group is selected and {@link #getCheckedRadioButtonId()} returns * null. *

* * @see #check(int) * @see #getCheckedRadioButtonId() */ public void clearCheck() { check(-1); } /** *

* Register a callback to be invoked when the checked radio button changes * in this group. *

* * @param listener * the callback to call on checked state change */ public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { mOnCheckedChangeListener = listener; } /** * {@inheritDoc} */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new CustomNestRadioGroup.LayoutParams(getContext(), attrs); } /** * {@inheritDoc} */ @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof CustomNestRadioGroup.LayoutParams; } @Override protected LinearLayout.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } /** *

* This set of layout parameters defaults the width and the height of the * children to {@link #WRAP_CONTENT} when they are not specified in the XML * file. Otherwise, this class ussed the value read from the XML file. *

* *

* See {@link android.R.styleable#LinearLayout_Layout LinearLayout * Attributes} for a list of all child view attributes that this class * supports. *

* */ public static class LayoutParams extends LinearLayout.LayoutParams { /** * {@inheritDoc} */ public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } /** * {@inheritDoc} */ public LayoutParams(int w, int h) { super(w, h); } /** * {@inheritDoc} */ public LayoutParams(int w, int h, float initWeight) { super(w, h, initWeight); } /** * {@inheritDoc} */ public LayoutParams(ViewGroup.LayoutParams p) { super(p); } /** * {@inheritDoc} */ public LayoutParams(MarginLayoutParams source) { super(source); } /** *

* Fixes the child's width to * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the * child's height to * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} when not * specified in the XML file. *

* * @param a * the styled attributes set * @param widthAttr * the width attribute to fetch * @param heightAttr * the height attribute to fetch */ @Override protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { if (a.hasValue(widthAttr)) { width = a.getLayoutDimension(widthAttr, "layout_width"); } else { width = WRAP_CONTENT; } if (a.hasValue(heightAttr)) { height = a.getLayoutDimension(heightAttr, "layout_height"); } else { height = WRAP_CONTENT; } } } /** *

* Interface definition for a callback to be invoked when the checked radio * button changed in this group. *

*/ public interface OnCheckedChangeListener { /** *

* Called when the checked radio button has changed. When the selection * is cleared, checkedId is -1. *

* * @param group * the group in which the checked radio button has changed * @param checkedId * the unique identifier of the newly checked radio button */ public void onCheckedChanged(CustomNestRadioGroup group, int checkedId); } private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // prevents from infinite recursion if (mProtectFromCheckedChange) { return; } mProtectFromCheckedChange = true; if (mCheckedId != -1) { setCheckedStateForView(mCheckedId, false); } mProtectFromCheckedChange = false; int id = buttonView.getId(); setCheckedId(id); } } /** *

* A pass-through listener acts upon the events and dispatches them to * another listener. This allows the table layout to set its own internal * hierarchy change listener without preventing the user to setup his. *

*/ private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener { private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener; /** * {@inheritDoc} */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public void onChildViewAdded(View parent, View child) { if (parent == CustomNestRadioGroup.this) { CompoundButton view = findCheckedView(child);// 查找子控件 if (view != null) { int id = view.getId(); // generates an id if it's missing if (id == View.NO_ID && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { id = View.generateViewId(); view.setId(id); } view.setOnCheckedChangeListener(mChildOnCheckedChangeListener); } } if (mOnHierarchyChangeListener != null) { mOnHierarchyChangeListener.onChildViewAdded(parent, child); } } /** * {@inheritDoc} */ public void onChildViewRemoved(View parent, View child) { if (parent == CustomNestRadioGroup.this) { CompoundButton view = findCheckedView(child);// 查找子控件 if (view != null) { view.setOnCheckedChangeListener(null); } } if (mOnHierarchyChangeListener != null) { mOnHierarchyChangeListener.onChildViewRemoved(parent, child); } } } }

这个类是从RadioGroup源码copy出来进行修改,增加向下递归查找RadioGroup方法即可。代码有注释。

二、MainActivity

package com.example.customgroupdemo;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.Toast;

import com.example.customgroupdemo.CustomNestRadioGroup;
import com.example.customgroupdemo.CustomNestRadioGroup.OnCheckedChangeListener;

public class MainActivity extends Activity implements OnCheckedChangeListener{

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		//修改RadioGroup源码后的自定义控件,可以查找RadioGroup里面不同布局RadioButton
		CustomNestRadioGroup CustomRadioGroup = (CustomNestRadioGroup)findViewById(R.id.CustomRadioGroup);
		if (CustomRadioGroup != null) CustomRadioGroup.setOnCheckedChangeListener(this);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onCheckedChanged(CustomNestRadioGroup group, int checkedId) {
		group.check(checkedId);
		if (group.getId() == R.id.CustomRadioGroup) {
			if (checkedId == R.id.radio0) {
				Toast.makeText(getApplicationContext(), "选择一", Toast.LENGTH_SHORT).show();
			}else if (checkedId == R.id.radio1) {
				Toast.makeText(getApplicationContext(), "选择二", Toast.LENGTH_SHORT).show();			
			}else if (checkedId == R.id.radio2) {
				Toast.makeText(getApplicationContext(), "选择三", Toast.LENGTH_SHORT).show();			
			}
		}
	}

}

自定义控件的使用,主要是通过完整的包名加上class名称来查找,可以看activity以及xml就能知道怎样调用自定义控件。

示例:com.example.customgroupdemo.CustomNestRadioGroup

三、xml



    

        

            

                

                

                    

                    
                

                

                

                    

                    
                

                

                

                    

                    
                
            
        
    

四、看看效果图

Android 自定义RadioGroup布局_第1张图片


欢迎交流学习各种自定义控件,源码学习。

转载请注明出处:单击此处

欢迎下载测试demo:单击此处

你可能感兴趣的:(Android,移动开发)