android的RadioButton的使用從來都讓人比較頭疼,如在佈局方面,圖案、文字沒法分別設置padding等,另外,低版本的android RadioGroup不支持換行排列的RadioButton(此bug在4.4以上貌似已經修復)java
這裏我自定義了一個VariedRadioButton,主要的功能優點有:android
1.能夠一步添加多個radio button,不須要在xml佈局文件中進行屢次羅列;app
2.靈活佈局:添加text、image的margin等屬性,能夠自由定義間隔;ide
3.靈活佈局:自由定義image/text的先後順序工具
4.靈活佈局:自由設定radio button的orientation,支持橫向和縱向佈局
5.無需添加響應radio button的oncheckedchanged接口。在須要取值時,直接調用一行代碼便可。this
效果以下:spa
代碼以下:3d
主界面:code
1 package cn.carbs.variedradiobutton; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.view.View; 6 import android.widget.Button; 7 import cn.carbs.variedradiobutton.view.VariedRadioButton; 8 9 public class MainActivity extends Activity { 10 11 VariedRadioButton variedRadioButton; 12 Button button; 13 @Override 14 protected void onCreate(Bundle savedInstanceState) { 15 super.onCreate(savedInstanceState); 16 setContentView(R.layout.activity_main); 17 variedRadioButton = (VariedRadioButton)findViewById(R.id.v); 18 button = (Button)findViewById(R.id.button); 19 button.setOnClickListener(new View.OnClickListener() { 20 21 @Override 22 public void onClick(View v) { 23 variedRadioButton.setSelectedIndex(4); 24 } 25 }); 26 27 variedRadioButton.setSelectedIndex(3); 28 } 29 30 31 }
自定義view的代碼:
1 package cn.carbs.variedradiobutton.view; 2 3 import java.util.ArrayList; 4 5 import android.content.Context; 6 import android.content.res.TypedArray; 7 import android.graphics.drawable.Drawable; 8 import android.util.AttributeSet; 9 import android.util.Log; 10 import android.util.TypedValue; 11 import android.view.Gravity; 12 import android.view.View; 13 import android.widget.ImageView; 14 import android.widget.LinearLayout; 15 import android.widget.TextView; 16 17 import cn.carbs.variedradiobutton.R; 18 import cn.carbs.variedradiobutton.util.DisplayUtil; 19 20 21 public class VariedRadioButton extends LinearLayout implements View.OnClickListener{ 22 23 private static final String TAG = "wang"; 24 25 private static final int ORDER_IMAGE_FIRST = 0; 26 private static final int ORDER_TEXT_FIRST = 1; 27 28 private static final int DEFAULT_SELECTED_INDEX = 0; 29 30 private static final float DEFAULT_MARGIN = 0f; 31 private static final int DEFAULT_ORDER = ORDER_IMAGE_FIRST; 32 private static final int DEFAULT_ORIENTATION = LinearLayout.HORIZONTAL; 33 private static final int DEFAULT_NUM = 2; 34 private static final int DEFAULT_TEXT_COLOR = 0xff000000; 35 private static final float DEFAULT_TEXT_VIEW_TEXT_SIZE_SP = 16; 36 37 private static final int DEFAULT_TEXTS_RES = 0; 38 private static final int DEFAULT_IMAGE_RES = 0; 39 40 private Context mContext; 41 private int mDrawableBackgroundRadioSelected; 42 private int mDrawableBackgroundRadio; 43 private Drawable mDrawableBackgroundText; 44 private float mTextMarginLeft = DEFAULT_MARGIN; 45 private float mTextMarginRight = DEFAULT_MARGIN; 46 private float mTextMarginTop = DEFAULT_MARGIN; 47 private float mTextMarginBottom = DEFAULT_MARGIN; 48 private float mImageMarginLeft = DEFAULT_MARGIN; 49 private float mImageMarginRight = DEFAULT_MARGIN; 50 private float mImageMarginTop = DEFAULT_MARGIN; 51 private float mImageMarginBottom = DEFAULT_MARGIN; 52 private float mUnitMarginLeft = DEFAULT_MARGIN; 53 private float mUnitMarginRight = DEFAULT_MARGIN; 54 private float mUnitMarginTop = DEFAULT_MARGIN; 55 private float mUnitMarginBottom = DEFAULT_MARGIN; 56 57 private int mOrder = DEFAULT_ORDER; 58 private int mOrientation = DEFAULT_ORIENTATION; 59 private int mNum = DEFAULT_NUM; 60 private int mTextColor = DEFAULT_TEXT_COLOR; 61 private float mTextSize = DEFAULT_TEXT_VIEW_TEXT_SIZE_SP; 62 private int mTextsRes = DEFAULT_TEXTS_RES; 63 private String[] mTexts; 64 private ArrayList<ImageView> mImageViews = new ArrayList(); 65 private ArrayList<TextView> mTextViews = new ArrayList(); 66 67 private View mContentView = null; 68 private LinearLayout mContainer = null; 69 70 private Object mTagTextView = new Object(); 71 private Object mTagImageView = new Object(); 72 73 private int mSelectedIndex = DEFAULT_SELECTED_INDEX; 74 75 public VariedRadioButton(Context context) { 76 this(context, null); 77 } 78 79 public VariedRadioButton(Context context, AttributeSet attrs) { 80 this(context, attrs, 0); 81 } 82 83 public VariedRadioButton(Context context, AttributeSet attrs, int defStyle) { 84 super(context, attrs, defStyle); 85 mContext = context; 86 mContentView = inflate(context, R.layout.view_varied_radio_button, this); 87 mContainer = (LinearLayout)mContentView.findViewById(R.id.container); 88 89 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.variedRadioButton); 90 91 final int count = a.getIndexCount(); 92 for (int i = 0; i < count; ++i) { 93 int attr = a.getIndex(i); 94 switch (attr) { 95 case R.styleable.variedRadioButton_backgroundRadioSelected: 96 mDrawableBackgroundRadioSelected = a.getResourceId(attr, DEFAULT_IMAGE_RES); 97 break; 98 case R.styleable.variedRadioButton_backgroundRadio: 99 mDrawableBackgroundRadio = a.getResourceId(attr, DEFAULT_IMAGE_RES); 100 break; 101 case R.styleable.variedRadioButton_backgroundText: 102 mDrawableBackgroundText = a.getDrawable(attr); 103 break; 104 case R.styleable.variedRadioButton_textMarginLeft: 105 mTextMarginLeft = a.getDimension(attr, DEFAULT_MARGIN); 106 break; 107 case R.styleable.variedRadioButton_textMarginRight: 108 mTextMarginRight = a.getDimension(attr, DEFAULT_MARGIN); 109 break; 110 case R.styleable.variedRadioButton_textMarginTop: 111 mTextMarginTop = a.getDimension(attr, DEFAULT_MARGIN); 112 break; 113 case R.styleable.variedRadioButton_textMarginBottom: 114 mTextMarginBottom = a.getDimension(attr, DEFAULT_MARGIN); 115 break; 116 case R.styleable.variedRadioButton_imageMarginLeft: 117 mImageMarginLeft = a.getDimension(attr, DEFAULT_MARGIN); 118 break; 119 case R.styleable.variedRadioButton_imageMarginRight: 120 mImageMarginRight = a.getDimension(attr, DEFAULT_MARGIN); 121 break; 122 case R.styleable.variedRadioButton_imageMarginTop: 123 mImageMarginTop = a.getDimension(attr, DEFAULT_MARGIN); 124 break; 125 case R.styleable.variedRadioButton_imageMarginBottom: 126 mImageMarginBottom = a.getDimension(attr, DEFAULT_MARGIN); 127 break; 128 case R.styleable.variedRadioButton_unitMarginLeft: 129 mUnitMarginLeft = a.getDimension(attr, DEFAULT_MARGIN); 130 break; 131 case R.styleable.variedRadioButton_unitMarginRight: 132 mUnitMarginRight = a.getDimension(attr, DEFAULT_MARGIN); 133 break; 134 case R.styleable.variedRadioButton_unitMarginTop: 135 mUnitMarginTop = a.getDimension(attr, DEFAULT_MARGIN); 136 break; 137 case R.styleable.variedRadioButton_unitMarginBottom: 138 mUnitMarginBottom = a.getDimension(attr, DEFAULT_MARGIN); 139 break; 140 case R.styleable.variedRadioButton_order: 141 mOrder = a.getInt(attr, DEFAULT_ORDER); 142 break; 143 case R.styleable.variedRadioButton_radioButtonNum: 144 mNum = a.getInt(attr, DEFAULT_NUM); 145 break; 146 case R.styleable.variedRadioButton_contentTextColor: 147 mTextColor = a.getColor(attr, DEFAULT_TEXT_COLOR); 148 break; 149 case R.styleable.variedRadioButton_contentTextSize: 150 mTextSize = DisplayUtil.px2sp(mContext, a.getDimensionPixelSize(attr, DisplayUtil.sp2px(mContext, DEFAULT_TEXT_VIEW_TEXT_SIZE_SP))); 151 break; 152 case R.styleable.variedRadioButton_optionsOrientation: 153 mOrientation = a.getInt(attr, DEFAULT_ORIENTATION); 154 break; 155 case R.styleable.variedRadioButton_texts: 156 mTextsRes = a.getResourceId(attr, DEFAULT_TEXTS_RES); 157 mTexts = mContext.getResources().getStringArray(mTextsRes); 158 break; 159 case R.styleable.variedRadioButton_selectedIndex: 160 mSelectedIndex = a.getInt(attr, DEFAULT_SELECTED_INDEX); 161 break; 162 163 } 164 } 165 a.recycle(); 166 167 mContainer.setOrientation(mOrientation); 168 LinearLayout.LayoutParams paramsUnit = null; 169 if(mOrientation == LinearLayout.HORIZONTAL){ 170 paramsUnit = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT); 171 }else{ 172 paramsUnit = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 0); 173 } 174 175 paramsUnit.weight = 1; 176 paramsUnit.leftMargin = (int)mUnitMarginLeft; 177 paramsUnit.rightMargin = (int)mUnitMarginRight; 178 paramsUnit.topMargin = (int)mUnitMarginTop; 179 paramsUnit.bottomMargin = (int)mUnitMarginBottom; 180 181 LinearLayout.LayoutParams paramsImageView = new LinearLayout.LayoutParams( 182 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); 183 paramsImageView.leftMargin = (int)mImageMarginLeft; 184 paramsImageView.rightMargin = (int)mImageMarginRight; 185 paramsImageView.topMargin = (int)mImageMarginTop; 186 paramsImageView.bottomMargin = (int)mImageMarginBottom; 187 188 LinearLayout.LayoutParams paramsTextView = new LinearLayout.LayoutParams( 189 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); 190 paramsTextView.leftMargin = (int)mTextMarginLeft; 191 paramsTextView.rightMargin = (int)mTextMarginRight; 192 paramsTextView.topMargin = (int)mTextMarginTop; 193 paramsTextView.bottomMargin = (int)mTextMarginBottom; 194 195 for(int n = 0; n < mNum; n++){ 196 197 LinearLayout ll = new LinearLayout(mContext); 198 ll.setOrientation(LinearLayout.HORIZONTAL); 199 ll.setGravity(Gravity.CENTER_VERTICAL); 200 201 ImageView image = new ImageView(mContext); 202 image.setBackgroundResource(mDrawableBackgroundRadio); 203 image.setLayoutParams(paramsImageView); 204 image.setTag(mTagImageView); 205 206 TextView text = new TextView(mContext); 207 text.setGravity(Gravity.CENTER); 208 if(n < mTexts.length){ 209 text.setText(mTexts[n]); 210 } 211 text.setLayoutParams(paramsTextView); 212 text.setTag(mTagTextView); 213 text.setTextSize(mTextSize); 214 text.setTextColor(mTextColor); 215 216 if(mOrder == ORDER_IMAGE_FIRST){ 217 ll.addView(image); 218 ll.addView(text); 219 }else{ 220 ll.addView(text); 221 ll.addView(image); 222 } 223 ll.setTag(n); 224 ll.setOnClickListener(this); 225 226 mImageViews.add(image); 227 mTextViews.add(text); 228 mContainer.addView(ll, paramsUnit); 229 } 230 mContainer.setWeightSum(mNum); 231 setSelectedIndex(mSelectedIndex); 232 } 233 234 public void setRadioButtonNum(int num){ 235 mNum = num; 236 } 237 238 public void setTextsRes(int textsRes){ 239 mTextsRes = textsRes; 240 mTexts = mContext.getResources().getStringArray(mTextsRes); 241 } 242 243 public void setTexts(String[] texts){ 244 mTexts = texts; 245 } 246 247 public void setSelectedIndex(int selectedIndex){ 248 if(selectedIndex >= 0 && selectedIndex < mNum){ 249 refreshView(selectedIndex); 250 }else{ 251 252 } 253 } 254 255 public int getSelectedIndex(){ 256 return mSelectedIndex; 257 } 258 259 @Override 260 public void onClick(View v) { 261 Integer index = (Integer)v.getTag(); 262 if(index != null){ 263 refreshView(index); 264 }else{ 265 throw new IllegalArgumentException("need to set a tag to LinearLayout element"); 266 } 267 } 268 269 private void refreshView(int selectedIndex){ 270 mSelectedIndex = selectedIndex; 271 LinearLayout clickedLL = null; 272 ImageView image = null; 273 for(int i = 0; i < mNum; i++){ 274 clickedLL = (LinearLayout)this.findViewWithTag(i); 275 image = (ImageView)clickedLL.findViewWithTag(mTagImageView); 276 if(i == selectedIndex){ 277 image.setBackgroundResource(mDrawableBackgroundRadioSelected); 278 }else{ 279 image.setBackgroundResource(mDrawableBackgroundRadio); 280 } 281 } 282 } 283 284 }
佈局文件:
activity_main.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:app="http://schemas.android.com/apk/res/cn.carbs.variedradiobutton" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 android:paddingLeft="@dimen/activity_horizontal_margin" 8 android:paddingRight="@dimen/activity_horizontal_margin" 9 android:paddingTop="@dimen/activity_vertical_margin" > 10 11 <cn.carbs.variedradiobutton.view.VariedRadioButton 12 android:id="@+id/v" 13 android:layout_width="match_parent" 14 android:layout_height="wrap_content" 15 android:background="#33333333" 16 android:text="@string/hello_world" 17 app:backgroundRadio="@drawable/button_unchecked" 18 app:backgroundRadioSelected="@drawable/button_checked" 19 app:backgroundText="#333333" 20 app:imageMarginLeft="30dp" 21 app:optionsOrientation="horizontal" 22 app:order="imageFirst" 23 app:radioButtonNum="5" 24 app:selectedIndex="1" 25 app:textMarginLeft="0dp" 26 app:texts="@array/city" /> 27 28 <Button 29 android:id="@+id/button" 30 android:layout_width="wrap_content" 31 android:layout_height="wrap_content" 32 android:text="button" /> 33 34 </LinearLayout>
view_varied_radio_button.xml :
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:id="@+id/container" 4 android:orientation="horizontal" 5 android:layout_width="match_parent" 6 android:layout_height="wrap_content" 7 android:gravity="center" > 8 9 10 </LinearLayout>
自定義屬性:
1 <?xml version="1.0" encoding="utf-8"?> 2 <resources> 3 4 <declare-styleable name="variedRadioButton"> 5 <attr name="backgroundRadio" /> 6 <attr name="backgroundRadioSelected" /> 7 <attr name="radioButtonNum" /> 8 <attr name="backgroundText" /> 9 <attr name="order" /> 10 <attr name="contentTextColor" /> 11 <attr name="contentTextSize" /> 12 <attr name="textMarginLeft" /> 13 <attr name="textMarginRight" /> 14 <attr name="textMarginTop" /> 15 <attr name="textMarginBottom" /> 16 <attr name="imageMarginLeft" /> 17 <attr name="imageMarginRight" /> 18 <attr name="imageMarginTop" /> 19 <attr name="imageMarginBottom" /> 20 <attr name="unitMarginLeft" /> 21 <attr name="unitMarginRight" /> 22 <attr name="unitMarginTop" /> 23 <attr name="unitMarginBottom" /> 24 <attr name="texts" /> 25 <attr name="optionsOrientation"> 26 <enum name="horizontal" value="0" /> 27 <enum name="vertical" value="1" /> 28 </attr> 29 <attr name="selectedIndex" /> 30 </declare-styleable> 31 32 <attr name="backgroundRadioSelected" format="reference|color" /> 33 <attr name="backgroundRadio" format="reference|color" /> 34 <attr name="radioButtonNum" format="reference|integer" /> 35 <attr name="backgroundText" format="reference|color" /> 36 <attr name="contentTextColor" format="reference|color" /> 37 <attr name="contentTextSize" format="reference|dimension" /> 38 <attr name="texts" format="reference" /> 39 <attr name="textMarginLeft" format="reference|dimension" /> 40 <attr name="textMarginRight" format="reference|dimension" /> 41 <attr name="textMarginTop" format="reference|dimension" /> 42 <attr name="textMarginBottom" format="reference|dimension" /> 43 <attr name="imageMarginLeft" format="reference|dimension" /> 44 <attr name="imageMarginRight" format="reference|dimension" /> 45 <attr name="imageMarginTop" format="reference|dimension" /> 46 <attr name="imageMarginBottom" format="reference|dimension" /> 47 48 <attr name="unitMarginLeft" format="reference|dimension" /> 49 <attr name="unitMarginRight" format="reference|dimension" /> 50 <attr name="unitMarginTop" format="reference|dimension" /> 51 <attr name="unitMarginBottom" format="reference|dimension" /> 52 53 <attr name="selectedIndex" format="reference|integer" /> 54 55 <attr name="order"> 56 <enum name="imageFirst" value="0" /> 57 <enum name="textFirst" value="1" /> 58 </attr> 59 60 </resources>
string資源:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">VariedRadioButton</string> <string name="action_settings">Settings</string> <string name="hello_world">Hello world!</string> <string-array name="city"> <item>中國</item> <item>美國</item> <item>俄羅斯</item> <item>英國</item> <item>德國</item> </string-array> </resources>
尺寸轉換工具類:(此類是在網上找的資源)
1 package cn.carbs.variedradiobutton.util; 2 3 import android.content.Context; 4 5 /** 6 * dp、sp 轉換爲 px 的工具類 7 */ 8 public class DisplayUtil { 9 /** 10 * 將px值轉換爲dip或dp值,保證尺寸大小不變 11 * 12 * @param pxValue 13 * @param scale 14 * @return 15 */ 16 public static int px2dip(Context context, float pxValue) { 17 final float scale = context.getResources().getDisplayMetrics().density; 18 return (int) (pxValue / scale + 0.5f); 19 } 20 21 /** 22 * 將dip或dp值轉換爲px值,保證尺寸大小不變 23 * 24 * @param dipValue 25 * @param scale 26 * @return 27 */ 28 public static int dip2px(Context context, float dipValue) { 29 final float scale = context.getResources().getDisplayMetrics().density; 30 return (int) (dipValue * scale + 0.5f); 31 } 32 33 /** 34 * 將px值轉換爲sp值,保證文字大小不變 35 * 36 * @param pxValue 37 * @param fontScale 38 * @return 39 */ 40 public static int px2sp(Context context, float pxValue) { 41 final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 42 return (int) (pxValue / fontScale + 0.5f); 43 } 44 45 /** 46 * 將sp值轉換爲px值,保證文字大小不變 47 * 48 * @param spValue 49 * @param fontScale 50 * @return 51 */ 52 public static int sp2px(Context context, float spValue) { 53 final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 54 return (int) (spValue * fontScale + 0.5f); 55 } 56 }
使用方法:
1.在xml佈局文件中:因爲用到了自定義屬性,所以須要添加命名空間xmlns:app="http://schemas.android.com/apk/res/cn.carbs.variedradiobutton"
<cn.carbs.variedradiobutton.view.VariedRadioButton android:id="@+id/v" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#33333333" android:text="@string/hello_world" app:backgroundRadio="@drawable/button_unchecked" app:backgroundRadioSelected="@drawable/button_checked" app:backgroundText="#333333" app:imageMarginLeft="30dp" app:optionsOrientation="horizontal" app:order="imageFirst" app:radioButtonNum="5" app:selectedIndex="1" app:textMarginLeft="0dp" app:texts="@array/city" />
原理很簡單:
VariedRadioButton繼承了ViewGroup(LinearLayout),經過代碼添加成對的imageview+textview來實現radiobutton的效果。
主要屬性的說明:
app:backgroundRadio 定義未被選中的radiobutton的背景
app:backgroundRadioSelected 定義已被選中的radiobutton的背景
app:backgroundText 定義textview的背景
app:imageMarginLeft 定義imageview距離左側控件間距
app:order="imageFirst" imageview在左
app:order="textFirst" 則是textview在左
app:radioButtonNum="5" 一共包含多少個「radiobutton」
app:selectedIndex="1" 設置初始的選中的按鈕,從0開始
app:texts="@array/city" 定義全部的「radiobutton」使用的string資源,若是array的length小於num,則後面的radiobutton的文字設置爲空