【原创】可以换行的RadioGroup

时间:2022-01-06 18:50:17

0、效果截图:

【原创】可以换行的RadioGroup

以上两个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" />

【原创】可以换行的RadioGroup

利用margin同样可以实现简单的RadioGroup内组件换行,感谢分享~~~