自定义控件:自定义RadioGroup和RadioButton,让布局有更多选择
2018-01-08 16:42
411 查看
转载请注明出处:http://blog.csdn.net/wriar/article/details/79004334
android的系统控件RadioGroup布局继承的是LinearLayout,所以只能实现这样单行或者单列的
也很容易实现
但是有时候会是这种多行多列布局
遇到这种情况RadioGroup就没用了。
况且单个选项的布局有可能会很复杂,RadioButton内又不能设置复杂布局
为了解决这个问题我写了一套自定义的RadioGroup和RadioButton,RadioGroup继承了GridLayout,已适配更复杂的布局情况,RadioButton继承了FrameLayout,里面的布局随便写
看效果图,动图我就不折腾了
贴代码:
android的系统控件RadioGroup布局继承的是LinearLayout,所以只能实现这样单行或者单列的
也很容易实现
但是有时候会是这种多行多列布局
遇到这种情况RadioGroup就没用了。
况且单个选项的布局有可能会很复杂,RadioButton内又不能设置复杂布局
为了解决这个问题我写了一套自定义的RadioGroup和RadioButton,RadioGroup继承了GridLayout,已适配更复杂的布局情况,RadioButton继承了FrameLayout,里面的布局随便写
看效果图,动图我就不折腾了
贴代码:
/** * 说明 * <p> * android:columnCount="3" 几列 * android:rowCount="3" 几行 * android:checkedButton="@+id/mrb1" 选中哪个 */ /** * @author hp * @ClassName MRadioGroup * @Description: android:checkedButton="@+id/mrb1" * @date 2018/1/5 0005 */ public class MRadioGroup extends GridLayout { // holds the checked id; the selection is empty by default private int mCheckedId = -1; private boolean mProtectFromCheckedChange = false; private OnCheckedChangeListener mOnCheckedChangeListener; private MRadioButton.OnCheckedChangeListener mChildOnCheckedChangeListener; private PassThroughHierarchyChangeListener mPassThroughListener; public MRadioGroup(Context context) { super(context); init(); } public MRadioGroup(Context context, AttributeSet attrs) { super(context, attrs); // retrieve selected radio button as requested by the user in the // XML layout file TypedArray attributes = context.obtainStyledAttributes( attrs, getInternalRS("styleable", "RadioGroup"), getInternalR("attr", "radioButtonStyle"), 0); int value = attributes.getResourceId(getInternalR("styleable", "RadioGroup_checkedButton"), View.NO_ID); if (value != View.NO_ID) { mCheckedId = value; Log.e("MINE", "setChecked mCheckedId:" + mCheckedId); } attributes.recycle(); init(); } private void init() { mChildOnCheckedChangeListener = new CheckedStateTracker(); mPassThroughListener = new PassThroughHierarchyChangeListener(); super.setOnHierarchyChangeListener(mPassThroughListener); } /** * <p>Register a callback to be invoked when the checked radio button * changes in this group.</p> * * @param listener the callback to call on checked state change */ public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { mOnCheckedChangeListener = listener; } /** * {@inheritDoc} */ @Override public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { // the user listener is delegated to our pass-through listener mPassThroughListener.mOnHierarchyChangeListener = listener; } /** * <p>Interface definition for a callback to be invoked when the checked * radio button changed in this group.</p> */ public interface OnCheckedChangeListener { /** * <p>Called when the checked radio button has changed. When the * selection is cleared, checkedId is -1.</p> * * @param group the group in which the checked radio button has changed * @param checkedId the unique identifier of the newly checked radio button */ public void onCheckedChanged(View group, @IdRes int checkedId); } /** * {@inheritDoc} */ @Override protected void onFinishInflate() { super.onFinishInflate(); // checks the appropriate radio button as requested in the XML file if (mCheckedId != -1) { mProtectFromCheckedChange = true; setCheckedStateForView(mCheckedId, true); mProtectFromCheckedChange = false; setCheckedId(mCheckedId); } } private class CheckedStateTracker implements MRadioButton.OnCheckedChangeListener { @Override public void onCheckedChanged(View buttonView, boolean isChecked) { // prevents from infinite recursion if (mProtectFromCheckedChange) { return; } mProtectFromCheckedChange = true; if (mCheckedId != -1) { setCheckedStateForView(mCheckedId, false); } mProtectFromCheckedChange = false; int id = buttonView.getId(); setCheckedId(id); } } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { if (child instanceof MRadioButton) { final MRadioButton button = (MRadioButton) child; if (button.isChecked()) { mProtectFromCheckedChange = true; if (mCheckedId != -1) { setCheckedStateForView(mCheckedId, false); } mProtectFromCheckedChange = false; setCheckedId(button.getId()); } } super.addView(child, index, params); } /** * <p>Sets the selection to the radio button whose identifier is passed in * parameter. Using -1 as the selection identifier clears the selection; * such an operation is equivalent to invoking {@link #clearCheck()}.</p> * * @param id the unique id of the radio button to select in this group * @see #getCheckedRadioButtonId() * @see #clearCheck() */ public void check(@IdRes int id) { // don't even bother if (id != -1 && (id == mCheckedId)) { return; } if (mCheckedId != -1) { setCheckedStateForView(mCheckedId, false); } if (id != -1) { setCheckedStateForView(id, true); } setCheckedId(id); } private void setCheckedId(@IdRes int id) { mCheckedId = id; if (mOnCheckedChangeListener != null) { mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId); } } private void setCheckedStateForView(int viewId, boolean checked) { View checkedView = findViewById(viewId); if (checkedView != null && checkedView instanceof MRadioButton) { ((MRadioButton) checkedView).setChecked(checked); } } /** * <p>Returns the identifier of the selected radio button in this group. * Upon empty selection, the returned value is -1.</p> * * @return the unique id of the selected radio button in this group * @attr ref android.R.styleable#RadioGroup_checkedButton * @see #check(int) * @see #clearCheck() */ @IdRes public int getCheckedRadioButtonId() { return mCheckedId; } /** * <p>Clears the selection. When the selection is cleared, no radio button * in this group is selected and {@link #getCheckedRadioButtonId()} returns * null.</p> * * @see #check(int) * @see #getCheckedRadioButtonId() */ public void clearCheck() { check(-1); } /** * <p>A pass-through listener acts upon the events and dispatches them * to another listener. This allows the table layout to set its own internal * hierarchy change listener without preventing the user to setup his.</p> */ private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener { private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener; /** * {@inheritDoc} */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) public void onChildViewAdded(View parent, View child) { if (parent == MRadioGroup.this && child instanceof MRadioButton) { int id = child.getId(); // generates an id if it's missing if (id == View.NO_ID) { id = View.generateViewId(); child.setId(id); } ((MRadioButton) child).setOnCheckedChangeWidgetListener( mChildOnCheckedChangeListener); } if (mOnHierarchyChangeListener != null) { mOnHierarchyChangeListener.onChildViewAdded(parent, child); } } /** * {@inheritDoc} */ public void onChildViewRemoved(View parent, View child) { if (parent == MRadioGroup.this && child instanceof MRadioButton) { ((MRadioButton) child).setOnCheckedChangeWidgetListener(null); } if (mOnHierarchyChangeListener != null) { mOnHierarchyChangeListener.onChildViewRemoved(parent, child); } } } public int getInternalR(String v1, String v2) { int titleStyle = 0; try { Class clasz = Class.forName("com.android.internal.R$" + v1);//styleable Field field = clasz.getDeclaredField(v2); field.setAccessible(true); titleStyle = (Integer) field.get(null); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } Log.w("MINE", "getInternalR:" + titleStyle); return titleStyle; } public int[] getInternalRS(String v1, String v2) { int[] textAppearanceStyleArr = new int[0]; try { Class clasz = Class.forName("com.android.internal.R$" + v1);//styleable Field field = clasz.getDeclaredField(v2); field.setAccessible(true); textAppearanceStyleArr = (int[]) field.get(null); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } Log.w("MINE", "getInternalRS:" + textAppearanceStyleArr); return textAppearanceStyleArr; } }
/** * 说明 * <p> * android:layout_column="1" 第几行 * android:layout_row="0" 第几列 * android:checked="true" 是否选中 * * 图片资源里面设置的是state_selected属性不是state_checked */ /** * @author hp * @ClassName MRadioButton * @Description: * @date 2018/1/5 0005 */ public class MRadioButton extends FrameLayout implements Checkable { private boolean mChecked; private OnCheckedChangeListener mOnCheckedChangeListener; private OnCheckedChangeListener mOnCheckedChangeWidgetListener; public MRadioButton(Context context) { this(context, null); } public MRadioButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MRadioButton(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MRadioButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr); defStyleAttr = getInternalR("attr", "radioButtonStyle"); final TypedArray a = context.obtainStyledAttributes( attrs, getInternalRS("styleable", "CompoundButton"), defStyleAttr, defStyleRes); if (a.hasValue(getInternalR("styleable", "CompoundButton_checked"))) { final boolean checked = a.getBoolean(getInternalR("styleable", "CompoundButton_checked"), false); setChecked(checked); } } @Override public boolean onTouchEvent(MotionEvent event) { Log.w("MINE", "MotionEvent:" + event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: performClick(); break; } return super.onTouchEvent(event); } /** * Register a callback to be invoked when the checked state of this button * changes. * * @param listener the callback to call on checked state change */ public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { mOnCheckedChangeListener = listener; } /** * Register a callback to be invoked when the checked state of this button * changes. This callback is used for internal purpose only. * * @param listener the callback to call on checked state change * @hide */ void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) { mOnCheckedChangeWidgetListener = listener; } @Override public void setChecked(boolean checked) { Log.e("MINE", "setChecked:" + checked); if (mChecked != checked) { mChecked = checked; setSelected(checked); refreshDrawableState(); if (mOnCheckedChangeListener != null) { mOnCheckedChangeListener.onCheckedChanged(this, mChecked); } if (mOnCheckedChangeWidgetListener != null) { mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked); } } } /** * {@inheritDoc} */ @Override protected void onFinishInflate() { super.onFinishInflate(); setSelected(mChecked); } @Override public void setSelected(boolean selected) { super.setSelected(selected); for (int i = 0; i < getChildCount(); i++) { View v = getChildAt(i); v.setSelected(selected); } } public int getInternalR(String v1, String v2) { int titleStyle = 0; try { Class clasz = Class.forName("com.android.internal.R$" + v1);//styleable Field field = clasz.getDeclaredField(v2); field.setAccessible(true); titleStyle = (Integer) field.get(null); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } Log.w("MINE", "getInternalR:" + titleStyle); return titleStyle; } public int[] getInternalRS(String v1, String v2) { int[] textAppearanceStyleArr = new int[0]; try { Class clasz = Class.forName("com.android.internal.R$" + v1);//styleable Field field = clasz.getDeclaredField(v2); field.setAccessible(true); textAppearanceStyleArr = (int[]) field.get(null); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } Log.w("MINE", "getInternalRS:" + textAppearanceStyleArr); return textAppearanceStyleArr; } /** * Interface definition for a callback to be invoked when the checked state * of a compound button changed. */ public static interface OnCheckedChangeListener { /** * Called when the checked state of a compound button has changed. * * @param buttonView The compound button view whose state has changed. * @param isChecked The new checked state of buttonView. */ void onCheckedChanged(View buttonView, boolean isChecked); } @Override public boolean isChecked() { return mChecked; } @Override public boolean performClick() { toggle(); final boolean handled = super.performClick(); if (!handled) { // View only makes a sound effect if the onClickListener was // called, so we'll need to make one here instead. playSoundEffect(SoundEffectConstants.CLICK); } return handled; } @Override public void toggle() { if (!isChecked()) { setChecked(!mChecked); } } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="jeremy.myprogresstest.MainActivity"> <jeremy.myprogresstest.MRadioGroup android:id="@+id/mrg" android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="3" android:rowCount="3"> <jeremy.myprogresstest.MRadioButton android:id="@+id/mrb1" android:layout_width="100dp" android:layout_height="100dp" android:layout_column="0" android:layout_row="0" android:background="@drawable/ic_admin_remember" android:checked="true" /> <jeremy.myprogresstest.MRadioButton android:id="@+id/mrb2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_column="2" android:layout_row="0" android:checked="false"> <LinearLayout android:layout_width="100dp" android:layout_height="100dp" android:background="@drawable/color_b2w" android:orientation="horizontal"> <ImageView android:layout_width="30dp" android:layout_height="30dp" android:layout_gravity="center_vertical" android:background="@drawable/ic_admin_remember" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="你好" android:textColor="@color/txt_w2b" android:textSize="18sp" /> </LinearLayout> </jeremy.myprogresstest.MRadioButton> <jeremy.myprogresstest.MRadioButton android:id="@+id/mrb3" android:layout_width="100dp" android:layout_height="100dp" android:layout_column="1" android:layout_row="1" android:background="@drawable/ic_admin_remember" /> <jeremy.myprogresstest.MRadioButton android:id="@+id/mrb4" android:layout_width="100dp" android:layout_height="100dp" android:layout_column="2" android:layout_row="2" android:background="@drawable/ic_admin_remember" /> <jeremy.myprogresstest.MRadioButton android:id="@+id/mrb5" android:layout_width="100dp" android:layout_height="100dp" android:layout_column="0" android:layout_row="2" android:background="@drawable/ic_admin_remember" /> </jeremy.myprogresstest.MRadioGroup> </LinearLayout>
相关文章推荐
- RadioGroup+RadioButton嵌套其他布局实现多行单选布局、自定义RadioButton选中和非选中样式、文字颜色
- Android开发自定RadioGroup实现多布局重叠并单选&修改radioButton按钮样式
- android 控件 单项选择(RadioGroup,RadioButton)
- 安卓ListView中使用RadioGroup进行RadioButton的单项选择
- 自定义radioGroup与radiobutton嵌套使用
- 动态布局中RadioGroup的RadioButton有时候不相互排斥的原因
- 自定义RadioGroup 实现选择
- Android:解决RadioGroup中RadioButton的图片自定义及每项间隔距离一样
- 支持自定义布局的RadioGroup
- 在RadioGroup中实现RadioButton的线性布局
- RadioGroup和RadioButton 的布局文件(xml)
- Android 应用开发笔记 - 单项选择(RadioGroup, RadioButton)
- Android中RadioGroup和RadioButton布局实例
- 单项选择功能RadioGroup和RadioButton的使用
- Android自定义RadioGroup中的RadioButton
- android自定义RadioGroup实现继承多种布局
- 自定义RadioGroup动态添加RadioButton,并获取选中radioButton的位置
- RadioGroup 的 RadioButton 选择改变字体颜色和背景颜色
- Android自定义控件】支持多层嵌套RadioButton的RadioGroup
- Android开发之四(五):常用控件之单项选择(RadioGroup和RadioButton)