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的更多相关文章

  1. [原创]css设置禁止中文换行

    white-space: nowrap;   如有需要还可以设置word-break,word-wrap配合.

  2. 《zw版·Halcon-delphi系列原创教程》 Halcon分类函数005·graphics-obj,基本绘图单元,包括线段、矩形、椭圆、圆形

    <zw版·Halcon-delphi系列原创教程> Halcon分类函数005·graphics-obj,基本绘图单元,包括线段.矩形.椭圆.圆形 graphics-obj,基本绘图单元, ...

  3. Android 自定义View实现多行RadioGroup (MultiLineRadioGroup)

    一.项目概况 我们都知道RadioGroup可以实现选择框,但它有一个局限性,由于它是继承自LinearLayout的,所以只能有一个方向,横向或者纵向:但有时候仅一行的RadioGroup并不能满足 ...

  4. 换行符javajava去除字符串中的空格、回车、换行符、制表符

    在改章节中,我们主要介绍换行符java的内容,自我感觉有个不错的建议和大家分享下     每日一道理 只有启程,才会到达理想和目的地,只有拼搏,才会获得辉煌的成功,只有播种,才会有收获.只有追求,才会 ...

  5. 《zw版·delphi与halcon系列原创教程》zw版_THOperatorSetX控件函数列表 v11中文增强版

    <zw版·delphi与halcon系列原创教程>zw版_THOperatorSetX控件函数列表v11中文增强版 Halcon虽然庞大,光HALCONXLib_TLB.pas文件,源码就 ...

  6. Linux和Windows的换行符

    一直对换行符这个东西概念比较模糊,直到最近花了一点时间仔细研究了一下,才彻底搞清楚这个问题,本文前面介绍部分是外文转载,后面例子是个人总结,希望能对大家有一些帮助. 回车符号和换行符号产生背景 关于“ ...

  7. ASP.NET过滤HTML标签只保留换行与空格的方法

    这篇文章主要介绍了ASP.NET过滤HTML标签只保留换行与空格的方法,包含网上常见的方法以及对此方法的改进,具有一定的参考借鉴价值,需要的朋友可以参考下   本文实例讲述了ASP.NET过滤HTML ...

  8. Android 自动换行流式布局的RadioGroup

    效果图 用法 使用FlowRadioGroup代替RadioGroup 代码 import android.content.Context; import android.util.Attribute ...

  9. WPF中的换行符

    原文:WPF中的换行符 WPF中UI上和后台代码中的换行符不同. 其中: XAML中为 C#代码中为 \r\n 或者: Environment.NewLine 版权声明:本文为博主原创文章,未经博主允 ...

随机推荐

  1. EndNote文献管理

    一直想写个博客,但是一直没有好好坐下来对自己工作进行一个梳理.从今天开始吧,争取多写一点. 今天,先介绍一下科技论文写作中经常使用的一款软件EndNote,这个软件,掌握它的使用方法后会觉得很方便:但 ...

  2. Windows环境Mycat数据库分库分表中间件部署

    下载地址MYCAT官方网站 jdk安装配置 首先去oracle官网下载并安装jdk8,添加环境变量,JAVA_HOME设置为D:\Worksoftware\Java\jdk1.8 CLASSPATH设 ...

  3. Swift学习(四)常量&变量&基础数据类型

    常量和变量 常量: 使用let关键词来声明一个常量 所指向的是一个特定类型的值,如数字10或者字符”hello”,常量的值是不能够被二次修改的 编程时使用常量能够让代码看起来更加安全和简洁! let ...

  4. 新安装的ubuntu系统如何设置中文输入法的方案

    本文是本人写的第一篇的linux博客,因为很菜,所以就把刚才自己安装中文输入法的过程给大家介绍一下,希望有所帮助. 1.首先,打开命令终端,两种方式,在Dash里面输入terminal然后enter, ...

  5. ajax success 和complete 的区别

    Function) success - 当请求成功时调用的函数.这个函数会得到一个参数:从服务器返回的数据(根据“dataType”进行了格式化). Function) complete - 当请求完 ...

  6. C# 最原始的tree 递归使用

    ; i < dr.Rows.Count; i++)             {                 )                 {                     n ...

  7. ASP.NET的SEO:使用.ashx文件——排除重复内容

    本系列目录 不同的链接指向的页面如果具有大量相同的内容,这种现象就会被称为"重复内容",如果一个网站的重复内容很多,搜索引擎就会认为这个网站的价值不高.所以我们应尽量避免各种重复内 ...

  8. 修改VS解决方案及工程名,解决如何打开高/版本VS项目

    对于VS2008等低版本与高版本VS之间的转换问题: 对照下面2个版本的不同点自由修改,切换到相应的版本文件(红字修改,灰色删除) ---------------------------------- ...

  9. webstorm & phpstorm破解

    webstorm: http://idea.qinxi1992.cn/ http://idea.goxz.gq http://v2mc.net:1017 http://idea.imsxm.com h ...

  10. angular $apply()以及$digest()讲解

    重点的东西放上面,说三遍: 记住的最重要的是ng是否能检测到你对于model的修改.如果它不能检测到,那么你就需要手动地调用$apply()! 记住的最重要的是ng是否能检测到你对于model的修改. ...