自定义android RadioButton View,添加较为灵活的布局处理方式
android的RadioButton的使用历来都让人比较头疼,如在布局方面,图案、文字无法分别设置padding等,另外,低版本的android RadioGroup不支持换行排列的RadioButton(此bug在4.4以上貌似已经修复)
这里我自定义了一个VariedRadioButton,主要的功能优势有:
1.可以一步添加多个radio button,不需要在xml布局文件中进行多次罗列;
2.灵活布局:添加text、image的margin等属性,可以自由定义间隔;
3.灵活布局:自由定义image/text的前后顺序
4.灵活布局:自由设定radio button的orientation,支持横向和纵向
5.无需添加响应radio button的oncheckedchanged接口。在需要取值时,直接调用一行代码即可。
效果如下:
代码如下:
主界面:
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的文字设置为空