【原创】可以换行的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 版权声明:本文为博主原创文章,未经博主允 ...
随机推荐
- 翻译:深入 AngularUI Router
原文地址:http://www.ng-newsletter.com/posts/angular-ui-router.html ui-router: https://angular-ui.github. ...
- 下载和使用 Open XML PowerTools
安装 Open XML SDK 2.5 首先,需要安装 Open XML SDK 2.5 ,从这个地址下载安装程序:http://www.microsoft.com/en-in/download/de ...
- 调用WEKA包进行kmeans聚类(java)
所用数据文件:data1.txt @RELATION data1 @ATTRIBUTE one REAL @ATTRIBUTE two REAL @DATA 0.184000 0.482000 0.1 ...
- Oozie 中各种类型的作业执行结果记录
一,提交的作业被SUSPEND,然后再被KILL,记录如下: TYPE1: appType WORKFLOW_JOB id 0000002-160516095026479-oozie-oozi-W e ...
- VB 读取csv文件数据
Public adoConn As New ADODB.Connection Private Sub csv() adoConn.ConnectionString = "Driver={Mi ...
- Duilib学习笔记《02》— 界面布局
1. 界面描述XML文件 Duilib主要是通过XML来进行界面的布局配置,程序通过读取并解析XML文件来创建对应的窗体.DuiLib的页面布局分为三类:窗体(Window).容器(Contain)和 ...
- 【Linux】Zabbix + MPM + msmtp + mutt 监控MySQL + 邮件报警
Zabbix部署参考博文 http://blog.sina.com.cn/s/blog_5611597901017oe0.html MPM安装配置参考博文和MPM官网下载地址 http://blog ...
- VC++2010下编译STLport,Boost
VC++2010下编译STLport,Boost 最近在想向Boost转移,努力掌握Boost代码的过程中, STLport版本:5.2.1 Boost版本:1.4.6.1 (1.4.7.0也OK) ...
- poj1840 哈希
虽然这题目我曾经在我们学校OJ上做过但是我那时候是用的暴力做的,这次我用的是哈希写的,我写这题目时候开始是在main函数里面写哈希感觉很麻烦很不清晰,然后我换用函数来写,清晰了很多,写完就AC了.用哈 ...
- 一款简单射击游戏IOS源码
源码描述: 一款基于cocos2d的简单设计游戏,并且也是一款基于cocos2d的简单射击游戏(含苹果IAD广告), 游戏操作很简单,哪个数字大就点击射击哪个.里面有苹果iad广告,功能简单完整,适合 ...