自定义控件:自定义RadioGroup和RadioButton,让布局有更多选择

转载请注明出处:http://blog.csdn.net/wriar/article/details/79004334


android的系统控件RadioGroup布局继承的是LinearLayout,所以只能实现这样单行或者单列的

自定义控件:自定义RadioGroup和RadioButton,让布局有更多选择_第1张图片自定义控件:自定义RadioGroup和RadioButton,让布局有更多选择_第2张图片

也很容易实现

但是有时候会是这种多行多列布局

自定义控件:自定义RadioGroup和RadioButton,让布局有更多选择_第3张图片

遇到这种情况RadioGroup就没用了。

况且单个选项的布局有可能会很复杂,RadioButton内又不能设置复杂布局

为了解决这个问题我写了一套自定义的RadioGroup和RadioButton,RadioGroup继承了GridLayout,已适配更复杂的布局情况,RadioButton继承了FrameLayout,里面的布局随便写

看效果图,动图我就不折腾了

自定义控件:自定义RadioGroup和RadioButton,让布局有更多选择_第4张图片

贴代码:

/**
 * 说明
 * 

* 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); } /** *

Register a callback to be invoked when the checked radio button * changes in this group.

* * @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; } /** *

Interface definition for a callback to be invoked when the checked * radio button changed in this group.

*/ public interface OnCheckedChangeListener { /** *

Called when the checked radio button has changed. When the * selection is cleared, checkedId is -1.

* * @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); } /** *

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()}.

* * @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); } } /** *

Returns the identifier of the selected radio button in this group. * Upon empty selection, the returned value is -1.

* * @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; } /** *

Clears the selection. When the selection is cleared, no radio button * in this group is selected and {@link #getCheckedRadioButtonId()} returns * null.

* * @see #check(int) * @see #getCheckedRadioButtonId() */ public void clearCheck() { check(-1); } /** *

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.

*/ 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; } }

/**
 * 说明
 * 

* 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); } } }




    

        

        

            

                

                
            
        

        

        

        
    




你可能感兴趣的:(自定义控件)