0、效果截图:
以上两个RadioGroup均使用FNRadioGroup实现。
1、控件代码:
1 public class FNRadioGroup extends ViewGroup { 2 3 /** 没有ID */ 4 private final static int NO_ID = -1; 5 6 /** 当前选中的子控件ID */ 7 private int mCheckedId = NO_ID; 8 9 /** 子控件选择改变监听器 */ 10 private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener; 11 12 /** 为true时,不处理子控件选择事件 */ 13 private boolean mProtectFromCheckedChange = false; 14 15 /** 选择改变监听器 */ 16 private OnCheckedChangeListener mOnCheckedChangeListener; 17 18 /** 子控件添加移除监听器 */ 19 private PassThroughHierarchyChangeListener mPassThroughListener; 20 21 /** 子控件左边距 */ 22 private int childMarginLeft = 0; 23 24 /** 子控件右边距 */ 25 private int childMarginRight = 0; 26 27 /** 子控件上边距 */ 28 private int childMarginTop = 0; 29 30 /** 子控件下边距 */ 31 private int childMarginBottom = 0; 32 33 /** 子空间高度 */ 34 private int childHeight; 35 36 /** 37 * 默认构造方法 38 */ 39 public FNRadioGroup(Context context) { 40 super(context); 41 init(); 42 } 43 44 /** 45 * XML实例构造方法 46 */ 47 public FNRadioGroup(Context context, AttributeSet attrs) { 48 super(context, attrs); 49 50 // 获取自定义属性checkedButton 51 TypedArray attributes = context.obtainStyledAttributes(attrs,R.styleable.FNRadioGroup) ; 52 // 读取默认选中id 53 int value = attributes.getResourceId(R.styleable.FNRadioGroup_checkedButton, NO_ID); 54 if (value != NO_ID) { 55 // 如果为设置checkButton属性,保持默认值NO_ID 56 mCheckedId = value; 57 } 58 // 读取子控件左边距 59 childMarginLeft = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginLeft, childMarginLeft); 60 if (childMarginLeft < 0) { 61 childMarginLeft = 0; 62 } 63 // 读取子控件右边距 64 childMarginRight = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginRight, childMarginRight); 65 if (childMarginRight < 0) { 66 childMarginRight = 0; 67 } 68 // 读取子控件上边距 69 childMarginTop = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginTop, childMarginTop); 70 if (childMarginTop < 0) { 71 childMarginTop = 0; 72 } 73 // 读取子控件下边距 74 childMarginBottom = attributes.getLayoutDimension(R.styleable.FNRadioGroup_childMarginBottom, childMarginBottom); 75 if (childMarginBottom < 0) { 76 childMarginBottom = 0; 77 } 78 attributes.recycle(); 79 // 调用二级构造 80 init(); 81 } 82 83 /** 84 * 设置子控件边距 85 * @param l 左边距 86 * @param t 上边距 87 * @param r 右边距 88 * @param b 下边距 89 */ 90 public void setChildMargin(int l, int t, int r, int b) { 91 childMarginTop = t; 92 childMarginLeft = l; 93 childMarginRight = r; 94 childMarginBottom = b; 95 } 96 97 /** 98 * 选中子控件为id的组件为选中项 99 */ 100 public void check(int id) { 101 if (id != -1 && (id == mCheckedId)) { 102 return; 103 } 104 if (mCheckedId != -1) { 105 setCheckedStateForView(mCheckedId, false); 106 } 107 if (id != -1) { 108 setCheckedStateForView(id, true); 109 } 110 setCheckedId(id); 111 } 112 113 /** 114 * 获取当前选中子控件的id 115 * @return 当前选中子控件的id 116 */ 117 public int getCheckedRadioButtonId() { 118 return mCheckedId; 119 } 120 121 /** 122 * 清除当前选中项 123 */ 124 public void clearCheck() { 125 check(-1); 126 } 127 128 /** 129 * 设置选中改变监听 130 * @param listener 选中改变监听 131 */ 132 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { 133 mOnCheckedChangeListener = listener; 134 } 135 136 /** 137 * 布局参数 138 */ 139 public static class LayoutParams extends ViewGroup.LayoutParams { 140 /** 141 * XML构造 142 * @param c 页面引用 143 * @param attrs XML属性集 144 */ 145 public LayoutParams(Context c, AttributeSet attrs) { 146 super(c, attrs); 147 } 148 /** 149 * 默认构造 150 * @param w 宽度 151 * @param h 高度 152 */ 153 public LayoutParams(int w, int h) { 154 super(w, h); 155 } 156 /** 157 * 父传递构造 158 * @param p ViewGroup.LayoutParams对象 159 */ 160 public LayoutParams(ViewGroup.LayoutParams p) { 161 super(p); 162 } 163 @Override 164 protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { 165 if (a.hasValue(widthAttr)) { 166 width = a.getLayoutDimension(widthAttr, "layout_width"); 167 } else { 168 width = WRAP_CONTENT; 169 } 170 if (a.hasValue(heightAttr)) { 171 height = a.getLayoutDimension(heightAttr, "layout_height"); 172 } else { 173 height = WRAP_CONTENT; 174 } 175 } 176 } 177 178 /** 179 * 项目选中改变监听器 180 */ 181 public interface OnCheckedChangeListener { 182 /** 183 * 选中项目改变回调 184 * @param group 组引用 185 * @param checkedId 改变的ID 186 */ 187 void onCheckedChanged(FNRadioGroup group, int checkedId); 188 } 189 190 /********************************************私有方法*******************************************/ 191 192 /** 193 * 二级构造方法 194 */ 195 private void init() { 196 197 // 初始化子控件选择监听 198 mChildOnCheckedChangeListener = new CheckedStateTracker(); 199 200 // 初始化子控件添加移除监听器 201 mPassThroughListener = new PassThroughHierarchyChangeListener(); 202 // 设置子控件添加移除监听器 203 super.setOnHierarchyChangeListener(mPassThroughListener); 204 } 205 @Override 206 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 207 ViewGroup.LayoutParams params = getLayoutParams(); 208 int pl = getPaddingLeft(); 209 int pr = getPaddingRight(); 210 int pt = getPaddingTop(); 211 int pb = getPaddingBottom(); 212 // 获取视图宽度 213 int width = MeasureSpec.getSize(widthMeasureSpec); 214 measureChildren(widthMeasureSpec, heightMeasureSpec); 215 // 计算Tag最大高度(以此作为所有tag的高度) 216 childHeight = 0; 217 for (int i = 0; i < getChildCount(); i++) { 218 int cmh = getChildAt(i).getMeasuredHeight(); 219 if (cmh > childHeight) { 220 childHeight = cmh; 221 } 222 } 223 // 计算本视图 224 if (params.height != LayoutParams.WRAP_CONTENT) { 225 // 非内容匹配的情况下 226 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 227 } else { 228 // 计算视图高度 229 int currentHeight = pt; 230 int currentWidth = pl; 231 for (int i = 0; i < getChildCount(); i++) { 232 View child = getChildAt(i); 233 int childWidth = child.getMeasuredWidth(); 234 // 本视图加入行中是否会超过视图宽度 235 if (currentWidth + childWidth + childMarginLeft + childMarginRight > width - pl - pr) { 236 // 累加行高读 237 currentHeight += childMarginTop + childMarginBottom + childHeight; 238 currentWidth = pl; 239 currentWidth += childMarginLeft + childMarginRight + childWidth; 240 } else { 241 // 累加行宽度 242 currentWidth += childMarginLeft + childMarginRight + childWidth; 243 } 244 } 245 currentHeight += childMarginTop + childMarginBottom + childHeight + pb; 246 super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(currentHeight, MeasureSpec.EXACTLY)); 247 } 248 } 249 @Override 250 protected void onLayout(boolean changed, int l, int t, int r, int b) { 251 int pl = getPaddingLeft(); 252 int pr = getPaddingRight(); 253 int pt = getPaddingTop(); 254 int pb = getPaddingBottom(); 255 int width = r - l; 256 // 布局Tag视图 257 int currentHeight = pt; 258 int currentWidth = pl; 259 for (int i=0; i < getChildCount(); i++) { 260 View child = getChildAt(i); 261 int childWidth = child.getMeasuredWidth(); 262 // 本视图加入行中是否会超过视图宽度 263 if (currentWidth + childWidth + childMarginLeft + childMarginRight > width - pl - pr) { 264 // 累加行高读 265 currentHeight += childMarginTop + childMarginBottom + childHeight; 266 currentWidth = pl; 267 // 布局视图 268 child.layout(currentWidth + childMarginLeft, currentHeight + childMarginTop, 269 currentWidth + childMarginLeft + childWidth, currentHeight + childMarginTop + childHeight); 270 currentWidth += childMarginLeft + childMarginRight + childWidth; 271 } else { 272 // 布局视图 273 child.layout(currentWidth + childMarginLeft, currentHeight + childMarginTop, 274 currentWidth + childMarginLeft + childWidth, currentHeight + childMarginTop + childHeight); 275 // 累加行宽度 276 currentWidth += childMarginLeft + childMarginRight + childWidth; 277 } 278 } 279 } 280 @Override 281 public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { 282 // 设置子空间添加移除监听 283 mPassThroughListener.mOnHierarchyChangeListener = listener; 284 } 285 @Override 286 protected void onFinishInflate() { 287 super.onFinishInflate(); 288 if (mCheckedId != NO_ID) { 289 // 如果读取到选中项,设置并存储选中项 290 mProtectFromCheckedChange = true; 291 setCheckedStateForView(mCheckedId, true); 292 mProtectFromCheckedChange = false; 293 setCheckedId(mCheckedId); 294 } 295 } 296 @Override 297 public void addView(View child, int index, ViewGroup.LayoutParams params) { 298 if (child instanceof RadioButton) { 299 final RadioButton button = (RadioButton) child; 300 if (button.isChecked()) { 301 mProtectFromCheckedChange = true; 302 if (mCheckedId != -1) { 303 setCheckedStateForView(mCheckedId, false); 304 } 305 mProtectFromCheckedChange = false; 306 setCheckedId(button.getId()); 307 } 308 } 309 310 super.addView(child, index, params); 311 } 312 private void setCheckedId(int id) { 313 mCheckedId = id; 314 if (mOnCheckedChangeListener != null) { 315 mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId); 316 } 317 } 318 private void setCheckedStateForView(int viewId, boolean checked) { 319 View checkedView = findViewById(viewId); 320 if (checkedView != null && checkedView instanceof RadioButton) { 321 ((RadioButton) checkedView).setChecked(checked); 322 } 323 } 324 @Override 325 public LayoutParams generateLayoutParams(AttributeSet attrs) { 326 return new FNRadioGroup.LayoutParams(getContext(), attrs); 327 } 328 @Override 329 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 330 return p instanceof RadioGroup.LayoutParams; 331 } 332 @Override 333 protected LayoutParams generateDefaultLayoutParams() { 334 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 335 } 336 @Override 337 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 338 super.onInitializeAccessibilityEvent(event); 339 event.setClassName(RadioGroup.class.getName()); 340 } 341 @Override 342 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 343 super.onInitializeAccessibilityNodeInfo(info); 344 info.setClassName(RadioGroup.class.getName()); 345 } 346 private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener { 347 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 348 // prevents from infinite recursion 349 if (mProtectFromCheckedChange) { 350 return; 351 } 352 mProtectFromCheckedChange = true; 353 if (mCheckedId != -1) { 354 setCheckedStateForView(mCheckedId, false); 355 } 356 mProtectFromCheckedChange = false; 357 int id = buttonView.getId(); 358 setCheckedId(id); 359 } 360 } 361 private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener { 362 private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener; 363 public void onChildViewAdded(View parent, View child) { 364 if (parent == FNRadioGroup.this && child instanceof RadioButton) { 365 int id = child.getId(); 366 // generates an id if it's missing 367 if (id == View.NO_ID) { 368 id = generateViewId(); 369 child.setId(id); 370 } 371 ((RadioButton) child).setOnCheckedChangeListener(mChildOnCheckedChangeListener); 372 } 373 374 if (mOnHierarchyChangeListener != null) { 375 mOnHierarchyChangeListener.onChildViewAdded(parent, child); 376 } 377 } 378 public void onChildViewRemoved(View parent, View child) { 379 if (parent == FNRadioGroup.this && child instanceof RadioButton) { 380 ((RadioButton) child).setOnCheckedChangeListener(null); 381 } 382 if (mOnHierarchyChangeListener != null) { 383 mOnHierarchyChangeListener.onChildViewRemoved(parent, child); 384 } 385 } 386 } 387 }
2、XML属性:
1 <declare-styleable name="FNRadioGroup"> 2 <attr name="checkedButton" format="integer" /> 3 <attr name="childMarginLeft" format="dimension"/> 4 <attr name="childMarginRight" format="dimension"/> 5 <attr name="childMarginTop" format="dimension"/> 6 <attr name="childMarginBottom" format="dimension"/> 7 </declare-styleable>
3、使用方法说明:
使用方法与RadioGroup相同,使用RadioButton作为子控件,
如果要实现网格样式,需要为子控件设置固定宽度
如果需要实现交错模式,将子控件宽度设置为WRAP_CONTENT即可。
如果需要设置子控件外边距,调用FNRadioGroup的setChildMargin方法设置即可。
PS:更多问题欢迎与我联系,如果需要转载请评论~~
后记:
网友补充了另一种实现方式如下:
1 <RadioButton 2 android:id="@+id/money_1500_Rb" 3 style="@style/radio_button_activity" 4 android:layout_marginLeft="-340dp" 5 android:layout_marginTop="50dp" 6 android:background="@drawable/bg_edittext" 7 android:gravity="center" 8 android:paddingBottom="@dimen/padding_10" 9 android:paddingTop="@dimen/padding_10" 10 android:text="2" />
利用margin同样可以实现简单的RadioGroup内组件换行,感谢分享~~~