你的位置:首页 > 操作系统

[操作系统]【原创】可以换行的RadioGroup


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    *  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    * 获取当前选中子控件的id115    * @return 当前选中子控件的id116   */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      * 142      * @param c 页面引用143      * @param attrs 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     @Override164     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 改变的ID186     */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   @Override206   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   @Override250   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   @Override281   public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {282     // 设置子空间添加移除监听283     mPassThroughListener.mOnHierarchyChangeListener = listener;284   }285   @Override286   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   @Override297   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   @Override325   public LayoutParams generateLayoutParams(AttributeSet attrs) {326     return new FNRadioGroup.LayoutParams(getContext(), attrs);327   }328   @Override329   protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {330     return p instanceof RadioGroup.LayoutParams;331   }332   @Override333   protected LayoutParams generateDefaultLayoutParams() {334     return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);335   }336   @Override337   public void onInitializeAccessibilityEvent(AccessibilityEvent event) {338     super.onInitializeAccessibilityEvent(event);339     event.setClassName(RadioGroup.class.getName());340   }341   @Override342   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 recursion349       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 missing367         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、

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内组件换行,感谢分享~~~