【原创】可以换行的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 版权声明:本文为博主原创文章,未经博主允 ...
随机推荐
- 剑指Offer:面试题13——在O(1)时间删除链表结点
问题描述: 给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点.链表结点与函数的定义如下: public class ListNode{ int value; ListNode ...
- Java后台工程师面试杂记——不跳不涨工资星人跳槽经历
经过接近一个月的时间,完成换工作这件“小事”,前后总计面试了多家公司,最后也没接到几个offer,不过最终总算尘埃落定,就对这个过程进行一个总结吧. 在某互联网公司工作了近一年的时间,但是频繁的业务需 ...
- nearly,about,almost的区别
nearly 几乎,将近almost 几乎,差一点儿就,差不多(与动词,副词,形容词以及名词连用时,可与nearly通用;与no,none,nothing,never等否定式连用时,不可与nearly ...
- 博客中 Flex4/Flash mp3音乐播放器实例 含演示地址
要求 必备知识 本文要求基本了解 Adobe Flex编程知识和JAVA基础知识. 开发环境 MyEclipse10/Flash Builder4.6/Flash Player11及以上 演示地址 演 ...
- cygwin中运行命令提示command not found的解决方法
在cygwin下运行ls等linux常见命令时出现“command not found”的提示,原因是环境变量没有配置好,因此只要将环境变量配置正确,即可正常使用.举例说明,cygwin安装在C盘根目 ...
- powerdesigner中怎么给一主键设为自增型auto increme
在使用powerdesigner 设计数据库表时,通常要对主键进行设置,如果主键是int 类型,一般会设置成自增,那么怎么在 powerdesigner 中设置呢,以下是具体的方法: 在所要设为自增型 ...
- line-height的小技巧
CSS中的line-height属性控制着文字的行间距离.通常被设置为一个无单位的值(例如:line-height:1.4),与文字尺寸是成比例的.它是排版中的一个重要的属性.太低了文字会挤在一起,太 ...
- WWF3入门<第一篇>
工作流是什么东西?暂时还不是很弄得清除. 工作流是用来解决什么问题的?暂时只是形成了一个很模糊的概念,还没办法用语言描述出来. 一.入门范例 以VS2008为例,先来创建一个WWF程序. 在工具箱中, ...
- MySQL初夜(乱码问题,命令行客户端使用)
一.乱码问题 装好MySQL,并且将数据从SQLServer导入到MySQL之后,程序一直报错. 解决方案: 首先,输入命令: show variables like "character_ ...
- ajax 清除缓存
$.ajax({ url : actionUrl , beforeSend :function(xmlHttp){ // deforeSend 是请求前清除缓存 ,如果没有缓存也不使用before ...