編輯:關於android開發
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 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內組件換行,感謝分享~~~
android 5.X Toolbar+DrawerLayout實現抽屜菜單 前言 ?android5.X新增的一個控件Toolbar,這個控件比ActionBar更
Android Studio SlidingMenu導入/配置 FloatMath找不到符號解決方法,studioslidingmenuSlidingMenu是一個第三方
React-Native系列Android——Native與Javascript通信原理(二) 前一篇博客分析了Native端向Javascript端通信的全流程,這
將語音搜索集成到Google Now中,googlenow原文標題:Use Voice Search to integrate with Google Now 原文鏈接: