【Android自定义控件】支持多层嵌套RadioButton的RadioGroup
Android 自定义 支持 控件 嵌套 多层 RadioButton RadioGroup
2023-09-14 09:00:59 时间
前言
非常喜欢用RadioButton+RadioGroup做Tabs,能自动处理选中等效果,但是自带的RadioGroup不支持嵌套RadioButton(从源码可看出仅仅是判断子控件是不是RadioButton),本文参考RadioGroup修改了一个支持嵌套CompoundButton的控件,非常实用。
声明
欢迎转载,但请保留文章原始出处:)
博客园:http://www.cnblogs.com
农民伯伯: http://over140.cnblogs.com
正文
![复制代码](http://common.cnblogs.com/images/copycode.gif)
* 支持嵌套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 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) {
// dont 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 childs width to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the childs
* 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 its 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);
}
}
}
![复制代码](http://common.cnblogs.com/images/copycode.gif)
}
代码说明
代码主要是仿照RadioGroup改写,主要是findCheckedView方法递归查找具有选中属性的子控件。
用法
![复制代码](http://common.cnblogs.com/images/copycode.gif)
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"
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
![复制代码](http://common.cnblogs.com/images/copycode.gif)
代码说明
1、实现非常常见的Tabs效果,结合ViewPager来使用,new_message_tips可以是一个类似微信右上角的小红圈,用来提醒有新的消息。
2、View.generateViewId需要4.2以上才能使用,所以最好自己设置id
转自:http://www.cnblogs.com/over140/p/3795877.html
相关文章
- android中适配器的作用,适配器模式 在Android中的简单理解「建议收藏」
- Android 上拉抽屉
- Android listView中的button点击事件[通俗易懂]
- android系统签名工具,android应用实现重启系统+签名「建议收藏」
- android declare-styleable 和style,Android 关于declare-styleable属性的写法….
- android 学习资料[通俗易懂]
- android toast居中显示_android Toast 弹出在屏幕中间位置以及自定义Toast
- android自定义toast样式_android设置对话框宽度
- 【Android Binder 系统】一、Binder 系统核心 ( IPC 进程间通信 | RPC 远程调用 )
- 【Android 安装包优化】开启资源压缩 ( 资源压缩配置 | 启用严格模式的资源引用检查 | 自定义保留/移除资源配置 | 资源压缩效果 )
- 【Android 逆向】加壳的 Android 应用启动流程 | 使用反射替换 LoadedApk 中的类加载器流程
- 【ijkplayer】编译 Android 版本的 ijkplayer ② ( 切换到 k0.8.8 分支 | 执行 init-android.sh 脚本进行初始化操作 )
- 【ijkplayer】编译 Android 版本的 ijkplayer ⑤ ( 执行 init-android-libyuv.sh | 执行 init-android-soundtouch.sh )
- 【Android Gradle 插件】自定义 Gradle 任务 ⑥ ( 执行 Gradle 任务的简化版命令 | 同时执行多个 Gradle 任务的命令 )
- 【Android Gradle 插件】Gradle 自定义 Plugin 插件 ⑤ ( 自定义插件中获定义方法 | 在插件中创建 Gradle 任务 | 代码示例 )
- 【Android Gradle 插件】将自定义 Gradle 插件上传到自建 Maven 仓库 ① ( Maven 仓库上传源码上传源码设置 | 自定义源码打包任务 | 自定义文档打包任务 )
- android上传图片至服务器详解手机开发
- [android] socket在手机上的应用详解手机开发
- [android] 切换按钮-自定义控件-拖动效果详解手机开发
- android Universal Image Loader for Android 说明文档 (1)详解手机开发
- 谷歌 Jetpack Compose 1.0 正式发布:打造原生 UI 的 Android 现代工具包
- Android 中的 Wi-Fi 直连方式的 Bug 会导致拒绝服务
- android自定义Android菜单背景的代码
- android在root模式下接听来电的方法