【Android自定义控件】支持多层嵌套RadioButton的RadioGroup
前言
非常喜欢用RadioButton+RadioGroup做Tabs,能自动处理选中等效果,但是自带的RadioGroup不支持嵌套RadioButton(从源码可看出仅仅是判断子控件是不是RadioButton),本文参考RadioGroup修改了一个支持嵌套CompoundButton的控件,非常实用。
欢迎转载,但请保留文章原始出处:)
博客园:http://www.cnblogs.com
农民伯伯: http://over140.cnblogs.com
正文
/**
* 支持嵌套CompoundButton的NestRadioGroup
*
* @author 农民伯伯 http://www.cnblogs.com/over140/
*
*/
public class NestRadioGroup 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 NestRadioGroup(Context context) {
super(context);
init();
} /**
* {@inheritDoc}
*/
public NestRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
} private void init() {
mCheckedId = View.NO_ID;
setOrientation(HORIZONTAL);
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);
} /**
* <p>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()}.</p>
*
* @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);
}
} /**
* <p>Returns the identifier of the selected radio button in this group.
* Upon empty selection, the returned value is -1.</p>
*
* @return the unique id of the selected radio button in this group
*
* @see #check(int)
* @see #clearCheck()
*
* @attr ref android.R.styleable#NestRadioGroup_checkedButton
*/
public int getCheckedRadioButtonId() {
return mCheckedId;
} /**
* <p>Clears the selection. When the selection is cleared, no radio button
* in this group is selected and {@link #getCheckedRadioButtonId()} returns
* null.</p>
*
* @see #check(int)
* @see #getCheckedRadioButtonId()
*/
public void clearCheck() {
check(-1);
} /**
* <p>Register a callback to be invoked when the checked radio button
* changes in this group.</p>
*
* @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 NestRadioGroup.LayoutParams(getContext(), attrs);
} /**
* {@inheritDoc}
*/
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof NestRadioGroup.LayoutParams;
} @Override
protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} /**
* <p>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.</p>
*
* <p>See
* {@link android.R.styleable#LinearLayout_Layout LinearLayout Attributes}
* for a list of all child view attributes that this class supports.</p>
*
*/
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);
} /**
* <p>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.</p>
*
* @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;
}
}
} /**
* <p>Interface definition for a callback to be invoked when the checked
* radio button changed in this group.</p>
*/
public interface OnCheckedChangeListener {
/**
* <p>Called when the checked radio button has changed. When the
* selection is cleared, checkedId is -1.</p>
*
* @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(NestRadioGroup 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);
}
} /**
* <p>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.</p>
*/
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 == NestRadioGroup.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 == NestRadioGroup.this) {
CompoundButton view = findCheckedView(child);//查找子控件
if (view != null) {
view.setOnCheckedChangeListener(null);
}
} if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}
}
代码说明
代码主要是仿照RadioGroup改写,主要是findCheckedView方法递归查找具有选中属性的子控件。
用法
<com.xxx.ui.view.NestRadioGroup
android:id="@+id/main_radio"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" > <RadioButton
android:id="@+id/radio_button0"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:checked="true"
android:text="@string/bottom_feed" /> <RadioButton
android:id="@+id/radio_button1"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="@string/bottom_square" /> <RelativeLayout
android:layout_width="0dip"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:orientation="horizontal" > <RadioButton
android:id="@+id/radio_button2"
style="@style/title_tab_text_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/bottom_message" /> <ImageView
android:id="@+id/new_message_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:layout_toRightOf="@+id/radio_button2"
android:src="@drawable/news_tips_red" />
</RelativeLayout>
</com.xxx.ui.view.NestRadioGroup>
代码说明
1、实现非常常见的Tabs效果,结合ViewPager来使用,new_message_tips可以是一个类似微信右上角的小红圈,用来提醒有新的消息。
2、View.generateViewId需要4.2以上才能使用,所以最好自己设置id
【Android自定义控件】支持多层嵌套RadioButton的RadioGroup的更多相关文章
- Android解决Fragment多层嵌套时onActivityResult无法正确回调的问题
前言: Fragment也可以使用startActivityForResult方法去打开一个Activity,然后在其onActivityResult方法中处理结果,可是当Fragment嵌套的时候, ...
- PHP之-json转数组,支持多层嵌套json
function json_to_array($str) { if (is_string($str)) $str = json_decode($str); $arr=array(); foreach( ...
- Android控件系列之RadioButton&RadioGroup(转)
学习目的: 1.掌握在Android中如何建立RadioGroup和RadioButton 2.掌握RadioGroup的常用属性 3.理解RadioButton和CheckBox的区别 4.掌握Ra ...
- Android 动态生成布局 (多层嵌套)
Android 除了能够载入xml文件,显示布局外,也能够代码生成布局,并通过setContentView(View view)方法显示布局.单独的一层布局,如一个主布局加一个控件(如Button\i ...
- Android控件系列之RadioButton&RadioGroup
学习目的: 1.掌握在Android中如何建立RadioGroup和RadioButton 2.掌握RadioGroup的常用属性 3.理解RadioButton和CheckBox的区别 4.掌握Ra ...
- android listView多层嵌套listView显示不全问题
最近在做项目,需要用到listVIew多层嵌套listVIew的需求,先发现已下两个处理办法比较好用 第一种: public class ListViewNesting extends ListVie ...
- Android笔记——Android自定义控件
目录: 1.自定义控件概述 01_什么是自定义控件 Android系统中,继承Android系统自带的View或者ViewGroup控件或者系统自带的控件,并在这基础上增加或者重新组合成我们想要的效果 ...
- Android自定义控件1--自定义控件介绍
Android控件基本介绍 Android本身提供了很多控件比如我们常用的有文本控件TextView和EditText:按钮控件Button和ImageButton状态开关按钮ToggleButton ...
- 自定义支持多行显示的RadioGroup
自定义支持多行显示的RadioGroup 原生的RadioGroup继承自LinearLayout,即只能支持一横排或者一竖排的排列显示RadioButton 现在改写RadioGroup,使它支持多 ...
随机推荐
- UEditor 1.4.3.1.NET版本上传配置备忘录
UEditor用起来还是挺方便的,下面是记录一下在上传中的配置问题(主要是上传图片的配置,其他的都是类似的). 我下载的是UEditor 1.4.3.1 .NET版本. 官网的配置说明地址为 http ...
- Mac下Vim配置语法高亮
设置终端的字体颜色 如图,打开终端然后,选择偏好设置,再选择描述文件,再窗口左侧可以选择系统配置好的,或者你也可以自定义,最后别忘了把你的配置设置成默认就行 Vim语法高亮设置 只需要找到vimrc配 ...
- 对于 ThreadLocal 的理解和应用
首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各 ...
- Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive.
调试网站时,异常出现:Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive. S ...
- LINQ的Any方法
返回布尔值,判断集合中是否有元素满足某一条件. source code: IEnumerable<string> str = new List<string> { " ...
- 【Remoting】.Net remoting方法实现简单的在线升级(下篇:重启exe)
一.前言 上篇运用了.Net Remoting技术解决了本地与服务器版本对比,并下载更新包的过程. 本篇主要是应用Process,来实现重启程序的过程. 情景假设: Revit2 ...
- C#使用读写锁三行代码简单解决多线程并发写入文件时线程同步的问题
(补充:初始化FileStream时使用包含文件共享属性(System.IO.FileShare)的构造函数比使用自定义线程锁更为安全和高效,更多内容可点击参阅) 在开发程序的过程中,难免少不了写入错 ...
- Mssql中一些常用数据类型的说明和区别
Mssql中一些常用数据类型的说明和区别 1.bigint 占用8个字节的存储空间,取值范围在-2^63 (-9,223,372,036,854,775,808) 到 2^63-1 (9,223,37 ...
- 【C#】添加引用方式抛出和捕获干净的WebService异常
说明:[干净]指的是客户端在捕获WebService(下称WS)抛出的异常时,得到的ex.Message就是WS方法中抛出的异常消息,不含任何“杂质”. 前提:你对WS有编写权.就是说如果你调的是别人 ...
- JSONObject和JSONArray的关系
JSON字符串的最上一层,肯定是一个JSONObject,JSONObject的下一层,可以包含JSONArray,JSONArray又包含了若干个JSONObject.用例子来说明: package ...