【原创】可以换行的RadioGroup
0、效果截图:

以上两个RadioGroup均使用FNRadioGroup实现。
1、控件代码:
public class FNRadioGroup extends ViewGroup {
/** 没有ID */
private final static int NO_ID = -1;
/** 当前选中的子控件ID */
private int mCheckedId = NO_ID;
/** 子控件选择改变监听器 */
private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
/** 为true时,不处理子控件选择事件 */
private boolean mProtectFromCheckedChange = false;
/** 选择改变监听器 */
private OnCheckedChangeListener mOnCheckedChangeListener;
/** 子控件添加移除监听器 */
private PassThroughHierarchyChangeListener mPassThroughListener;
/** 子控件左边距 */
private int childMarginLeft = 0;
/** 子控件右边距 */
private int childMarginRight = 0;
/** 子控件上边距 */
private int childMarginTop = 0;
/** 子控件下边距 */
private int childMarginBottom = 0;
/** 子空间高度 */
private int childHeight;
/**
* 默认构造方法
*/
public FNRadioGroup(Context context) {
super(context);
init();
}
/**
* XML实例构造方法
*/
public FNRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
// 获取自定义属性checkedButton
TypedArray attributes = context.obtainStyledAttributes(attrs,R.styleable.FNRadioGroup) ;
// 读取默认选中id
int value = attributes.getResourceId(R.styleable.FNRadioGroup_checkedButton, NO_ID);
if (value != NO_ID) {
// 如果为设置checkButton属性,保持默认值NO_ID
mCheckedId = value;
}
// 读取子控件左边距
childMarginLeft = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginLeft, childMarginLeft);
if (childMarginLeft < 0) {
childMarginLeft = 0;
}
// 读取子控件右边距
childMarginRight = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginRight, childMarginRight);
if (childMarginRight < 0) {
childMarginRight = 0;
}
// 读取子控件上边距
childMarginTop = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginTop, childMarginTop);
if (childMarginTop < 0) {
childMarginTop = 0;
}
// 读取子控件下边距
childMarginBottom = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginBottom, childMarginBottom);
if (childMarginBottom < 0) {
childMarginBottom = 0;
}
attributes.recycle();
// 调用二级构造
init();
}
/**
* 设置子控件边距
* @param l 左边距
* @param t 上边距
* @param r 右边距
* @param b 下边距
*/
public void setChildMargin(int l, int t, int r, int b) {
childMarginTop = t;
childMarginLeft = l;
childMarginRight = r;
childMarginBottom = b;
}
/**
* 选中子控件为id的组件为选中项
*/
public void check(int id) {
if (id != -1 && (id == mCheckedId)) {
return;
}
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
if (id != -1) {
setCheckedStateForView(id, true);
}
setCheckedId(id);
}
/**
* 获取当前选中子控件的id
* @return 当前选中子控件的id
*/
public int getCheckedRadioButtonId() {
return mCheckedId;
}
/**
* 清除当前选中项
*/
public void clearCheck() {
check(-1);
}
/**
* 设置选中改变监听
* @param listener 选中改变监听
*/
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
/**
* 布局参数
*/
public static class LayoutParams extends ViewGroup.LayoutParams {
/**
* XML构造
* @param c 页面引用
* @param attrs XML属性集
*/
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
/**
* 默认构造
* @param w 宽度
* @param h 高度
*/
public LayoutParams(int w, int h) {
super(w, h);
}
/**
* 父传递构造
* @param p ViewGroup.LayoutParams对象
*/
public LayoutParams(ViewGroup.LayoutParams p) {
super(p);
}
@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;
}
}
}
/**
* 项目选中改变监听器
*/
public interface OnCheckedChangeListener {
/**
* 选中项目改变回调
* @param group 组引用
* @param checkedId 改变的ID
*/
void onCheckedChanged(FNRadioGroup group, int checkedId);
}
/********************************************私有方法*******************************************/
/**
* 二级构造方法
*/
private void init() {
// 初始化子控件选择监听
mChildOnCheckedChangeListener = new CheckedStateTracker();
// 初始化子控件添加移除监听器
mPassThroughListener = new PassThroughHierarchyChangeListener();
// 设置子控件添加移除监听器
super.setOnHierarchyChangeListener(mPassThroughListener);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup.LayoutParams params = getLayoutParams();
int pl = getPaddingLeft();
int pr = getPaddingRight();
int pt = getPaddingTop();
int pb = getPaddingBottom();
// 获取视图宽度
int width = MeasureSpec.getSize(widthMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 计算Tag最大高度(以此作为所有tag的高度)
childHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
int cmh = getChildAt(i).getMeasuredHeight();
if (cmh > childHeight) {
childHeight = cmh;
}
}
// 计算本视图
if (params.height != LayoutParams.WRAP_CONTENT) {
// 非内容匹配的情况下
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
// 计算视图高度
int currentHeight = pt;
int currentWidth = pl;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
// 本视图加入行中是否会超过视图宽度
if (currentWidth + childWidth + childMarginLeft + childMarginRight > width - pl - pr) {
// 累加行高读
currentHeight += childMarginTop + childMarginBottom + childHeight;
currentWidth = pl;
currentWidth += childMarginLeft + childMarginRight + childWidth;
} else {
// 累加行宽度
currentWidth += childMarginLeft + childMarginRight + childWidth;
}
}
currentHeight += childMarginTop + childMarginBottom + childHeight + pb;
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(currentHeight, MeasureSpec.EXACTLY));
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int pl = getPaddingLeft();
int pr = getPaddingRight();
int pt = getPaddingTop();
int pb = getPaddingBottom();
int width = r - l;
// 布局Tag视图
int currentHeight = pt;
int currentWidth = pl;
for (int i=0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
// 本视图加入行中是否会超过视图宽度
if (currentWidth + childWidth + childMarginLeft + childMarginRight > width - pl - pr) {
// 累加行高读
currentHeight += childMarginTop + childMarginBottom + childHeight;
currentWidth = pl;
// 布局视图
child.layout(currentWidth + childMarginLeft, currentHeight + childMarginTop,
currentWidth + childMarginLeft + childWidth, currentHeight + childMarginTop + childHeight);
currentWidth += childMarginLeft + childMarginRight + childWidth;
} else {
// 布局视图
child.layout(currentWidth + childMarginLeft, currentHeight + childMarginTop,
currentWidth + childMarginLeft + childWidth, currentHeight + childMarginTop + childHeight);
// 累加行宽度
currentWidth += childMarginLeft + childMarginRight + childWidth;
}
}
}
@Override
public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
// 设置子空间添加移除监听
mPassThroughListener.mOnHierarchyChangeListener = listener;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (mCheckedId != NO_ID) {
// 如果读取到选中项,设置并存储选中项
mProtectFromCheckedChange = true;
setCheckedStateForView(mCheckedId, true);
mProtectFromCheckedChange = false;
setCheckedId(mCheckedId);
}
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof RadioButton) {
final RadioButton button = (RadioButton) child;
if (button.isChecked()) {
mProtectFromCheckedChange = true;
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(button.getId());
}
}
super.addView(child, index, params);
}
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 RadioButton) {
((RadioButton) checkedView).setChecked(checked);
}
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new FNRadioGroup.LayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof RadioGroup.LayoutParams;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(RadioGroup.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(RadioGroup.class.getName());
}
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);
}
}
private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener {
private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
public void onChildViewAdded(View parent, View child) {
if (parent == FNRadioGroup.this && child instanceof RadioButton) {
int id = child.getId();
// generates an id if it's missing
if (id == View.NO_ID) {
id = generateViewId();
child.setId(id);
}
((RadioButton) child).setOnCheckedChangeListener(mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}
public void onChildViewRemoved(View parent, View child) {
if (parent == FNRadioGroup.this && child instanceof RadioButton) {
((RadioButton) child).setOnCheckedChangeListener(null);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}
}
2、XML属性:
<declare-styleable name="FNRadioGroup">
<attr name="checkedButton" format="integer" />
<attr name="childMarginLeft" format="dimension"/>
<attr name="childMarginRight" format="dimension"/>
<attr name="childMarginTop" format="dimension"/>
<attr name="childMarginBottom" format="dimension"/>
</declare-styleable>
3、使用方法说明:
使用方法与RadioGroup相同,使用RadioButton作为子控件,
如果要实现网格样式,需要为子控件设置固定宽度
如果需要实现交错模式,将子控件宽度设置为WRAP_CONTENT即可。
如果需要设置子控件外边距,调用FNRadioGroup的setChildMargin方法设置即可。
PS:更多问题欢迎与我联系,如果需要转载请评论~~
后记:
网友补充了另一种实现方式如下:
<RadioButton
android:id="@+id/money_1500_Rb"
style="@style/radio_button_activity"
android:layout_marginLeft="-340dp"
android:layout_marginTop="50dp"
android:background="@drawable/bg_edittext"
android:gravity="center"
android:paddingBottom="@dimen/padding_10"
android:paddingTop="@dimen/padding_10"
android:text="2" />

利用margin同样可以实现简单的RadioGroup内组件换行,感谢分享~~~
【原创】可以换行的RadioGroup的更多相关文章
- [原创]css设置禁止中文换行
white-space: nowrap; 如有需要还可以设置word-break,word-wrap配合.
- 《zw版·Halcon-delphi系列原创教程》 Halcon分类函数005·graphics-obj,基本绘图单元,包括线段、矩形、椭圆、圆形
<zw版·Halcon-delphi系列原创教程> Halcon分类函数005·graphics-obj,基本绘图单元,包括线段.矩形.椭圆.圆形 graphics-obj,基本绘图单元, ...
- Android 自定义View实现多行RadioGroup (MultiLineRadioGroup)
一.项目概况 我们都知道RadioGroup可以实现选择框,但它有一个局限性,由于它是继承自LinearLayout的,所以只能有一个方向,横向或者纵向:但有时候仅一行的RadioGroup并不能满足 ...
- 换行符javajava去除字符串中的空格、回车、换行符、制表符
在改章节中,我们主要介绍换行符java的内容,自我感觉有个不错的建议和大家分享下 每日一道理 只有启程,才会到达理想和目的地,只有拼搏,才会获得辉煌的成功,只有播种,才会有收获.只有追求,才会 ...
- 《zw版·delphi与halcon系列原创教程》zw版_THOperatorSetX控件函数列表 v11中文增强版
<zw版·delphi与halcon系列原创教程>zw版_THOperatorSetX控件函数列表v11中文增强版 Halcon虽然庞大,光HALCONXLib_TLB.pas文件,源码就 ...
- Linux和Windows的换行符
一直对换行符这个东西概念比较模糊,直到最近花了一点时间仔细研究了一下,才彻底搞清楚这个问题,本文前面介绍部分是外文转载,后面例子是个人总结,希望能对大家有一些帮助. 回车符号和换行符号产生背景 关于“ ...
- ASP.NET过滤HTML标签只保留换行与空格的方法
这篇文章主要介绍了ASP.NET过滤HTML标签只保留换行与空格的方法,包含网上常见的方法以及对此方法的改进,具有一定的参考借鉴价值,需要的朋友可以参考下 本文实例讲述了ASP.NET过滤HTML ...
- Android 自动换行流式布局的RadioGroup
效果图 用法 使用FlowRadioGroup代替RadioGroup 代码 import android.content.Context; import android.util.Attribute ...
- WPF中的换行符
原文:WPF中的换行符 WPF中UI上和后台代码中的换行符不同. 其中: XAML中为 C#代码中为 \r\n 或者: Environment.NewLine 版权声明:本文为博主原创文章,未经博主允 ...
随机推荐
- opencv编程解决warning C4003: “max”宏的实参不足
忘了把程序出错的代码附上了,运行修改好的程序才发现的.只好把问题的代码大致写一下了: warning C4003: “min”宏的实参不足 error C2589: “(”:“::”右边的非法标记 e ...
- CODESOFT对话框中的显示字体怎么修改
不同的人其使用软件的视觉习惯是不一样的,直接给大家介绍过如何设置CODESOFT的界面语言,这是一个大范围的界面显示设置.本文,将介绍如何修改CODESOFT对话框显示的字体,以满足自己的视觉习惯 ...
- maven + appium + testng + java之pom.xml
参考来源:<https://search.maven.org/remotecontent?filepath=io/appium/java-client/3.3.0/java-client-3.3 ...
- 【caffe-windows】 caffe-master 之 mnist 超详细
本教程尽量详细,大多步骤都有图,如果运行出错,请先对照自己的文件是否和图上的一样,包括标点啊,空格啊,斜杠,反斜杠啊之类的小细节. 第一步: 官网下载mnist数据 http://yann.lec ...
- CSS3里的常用选择器总结
选择器 属性选择器: img[src="images/2.jpg"] 开头匹配: a[href ^="page/"] ...
- 关于lambda表达式在javascript中的使用
了解过js函数的同学应该都知道js的函数有很多种创建方式. 如: function fun(){}: var fun=function(){}: 但最近的学习中发现了lambda表达式型的创建js的匿 ...
- 数据结构-多级指针单链表(C语言)
偶尔看到大一时候写了一个多级链表,听起来好有趣,稍微整理一下. 稍微注意一下两点: 1.指针是一个地址,他自己也是有一个地址.一级指针(带一个*号)表示一级地址,他自身地址为二级地址.二级指针(带两个 ...
- 学习练习 Java冒泡排序 二分查找法
冒泡排序: // 冒泡排序 /* System.out.println("请输入要排序的个数:"); Scanner v = new Scanner(System.in); int ...
- NULL值比较,两个列的合并,列值按条件替换。
show create table 表名 -- 显示创建表的sql语句. 为已有的表增加新列.alter table 表名 add 列名 int NULL -- 此行加了一个int 类型 默认可以nu ...
- 关闭窗体后,利用StreamWriter保存控件里面的数据
以保存DataGridView里面的数据为例: 通过窗体增加的数据,没有用数据库保存,可以使用StreamWriter将数据存在临时文件里面,再次打开窗体时写入即可. private void For ...