RadioGroup內兼容任何佈局

網上搜索,RadioGroup自定義佈局應該能搜過到相應文章,具體誰是原創在下也分不清楚了。這裏借用一下這個類:CustomNestRadioGroup,這是從LinearLayout繼承的擴展類。使用該擴展類,可實現RadioButton的簡單佈局,
效果圖:
這裏寫圖片描述
這個效果就是垂直佈局,在RadioButton前面添加了圖標和文字內容。CustomNestRadioGroup 這個類就是爲此而生的,所以實現起來很容易。
上圖效果佈局:

 <com.sydb.common.widgets.CustomNestRadioGroup
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <RelativeLayout
                android:id="@+id/award_comment"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="10dp" >

                <RadioButton
                    android:id="@+id/check_alipay"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_margin="5dp"
                    android:background="@drawable/radio_button_style"
                    android:button="@null" />

                <ImageView
                    android:id="@+id/alipay_icon"
                    android:layout_width="36dp"
                    android:layout_height="36dp"
                    android:layout_alignParentLeft="true"
                    android:layout_centerVertical="true"
                    android:src="@drawable/alipay" />

                <TextView
                    android:id="@+id/alipay_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:layout_marginLeft="10dp"
                    android:layout_toRightOf="@id/alipay_icon"
                    android:text="@string/use_alipay"
                    android:textSize="@dimen/medium_text_size" />
            </RelativeLayout>

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="10dp"
                android:background="@color/grey2" />

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="10dp" >

                <RadioButton
                    android:id="@+id/check_wx_pay"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_margin="5dp"
                    android:background="@drawable/radio_button_style"
                    android:button="@null" />

                <ImageView
                    android:id="@+id/wx_pay_icon"
                    android:layout_width="36dp"
                    android:layout_height="36dp"
                    android:layout_alignParentLeft="true"
                    android:layout_centerVertical="true"
                    android:src="@drawable/wx_pay" />

                <TextView
                    android:id="@+id/wx_pay_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:layout_marginLeft="10dp"
                    android:layout_toRightOf="@id/wx_pay_icon"
                    android:text="@string/wx_pay"
                    android:textSize="@dimen/medium_text_size" />
            </RelativeLayout>
        </com.sydb.common.widgets.CustomNestRadioGroup>

效果圖二:
這裏寫圖片描述
現在想要用RadioButton實現下面這樣的效果,通過各種嘗試後,發現CustomNestRadioGroup 這個擴展類在也無法達到目的,如果設置垂直佈局<com.sydb.common.widgets.CustomNestRadioGroup android:orientation="vertical",這時只有1元20元是有效果的,而其他radioButton都是單獨成組,沒有互斥效果。而設置爲水平佈局後,第二行乾脆就顯示不出來了。爲了解決這個問題,甚至想到使用兩個CustomNestRadioGroup 水平佈局來實現顯示效果,用java代碼來控制選擇互斥的效果。當然只是想想而以,各種情況考慮後還是來看看CustomNestRadioGroup 這個類再說。

來來回回初略看了幾遍,並沒有去理解每句代碼什麼含意,看了裏面的幾個方法,重點發在了,addView和findCheckedView兩個方法上了,addView方法名看出一點意思,findCheckedView方法則是找出有checkable屬性的控件,找到後添加到佈局中。
細看findCheckedView方法,

        /** 遞歸查找具有選中屬性的子控件 */
    private static CompoundButton findCheckedView(View child) {
        //如果是控件,則返回,
        if (child instanceof CompoundButton) return (CompoundButton) child;
        //如果是容器(RelativeLayout、LinearLayout等)就尋找子控件
        if (child instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) child;
            //循環查詢子控件
            for (int i = 0, j = group.getChildCount(); i < j; i++) {

                CompoundButton check = findCheckedView(group.getChildAt(i));
                //找到了一個控件了,返回控件。
                if (check != null) return check;
            }
        }
        return null;// 沒有找到
    }

到現在可以知道爲什麼這時只有1元20元是有效果的了,因爲這個方法找到第一個radioButton後就不再繼續查找這個佈局容器中的其他RadioButton控件了。

我們需要的是把CustomNestRadioGroup 內部所有的RadioButton都查找並返回。這且不是很簡單,來看修改後的findCheckedView方法,使用List把在同一個容器中找到的RadioButton都存起來,遞歸完後,將找到的控件全部返回。

/** 遞歸查找具有選中屬性的子控件 */
    private static List<CompoundButton> findCheckedView(View child) {
        List<CompoundButton> list = new ArrayList<CompoundButton>();
        if (child instanceof CompoundButton) {
            list.add((CompoundButton) child);
            return list;
        }
        if (child instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) child;
            for (int i = 0, j = group.getChildCount(); i < j; i++) {
                List<CompoundButton> list2 = findCheckedView(group.getChildAt(i));
                list.addAll(list2);
            }
        }
        return list;// 沒有找到
    }

addView方法使用到了findCheckedView,這個方法也要作相應調整。

@Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        final List<CompoundButton> views = findCheckedView(child);
        for (CompoundButton view : views) {
            if (view != null) {
                if (view.isChecked()) {
                    mProtectFromCheckedChange = true;
                    if (mCheckedId != -1) {
                        setCheckedStateForView(mCheckedId, false);
                    }
                    mProtectFromCheckedChange = false;
                    setCheckedId(view.getId());
                }
            }
        }
        super.addView(child, index, params);
    }

調整了以上兩個地方後,就實現了效果圖二的內容了。
效果圖二佈局:

<com.sydb.common.widgets.CustomNestRadioGroup
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="10dp"
                android:gravity="center_vertical|center_horizontal" >

                <RadioButton
                    android:id="@+id/one"
                    android:layout_width="40dp"
                    android:layout_height="35dp"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="10dp"
                    android:layout_weight="1"
                    android:background="@drawable/radio_button_rechange_amount_background"
                    android:button="@null"
                    android:checked="true"
                    android:gravity="center_vertical|center_horizontal"
                    android:padding="2dp"
                    android:text="@string/activity_recharge_mount_1"
                    android:textColor="@drawable/textcolor_rechange_amount_background"
                    android:textSize="@dimen/medium_text_size" />

                <RadioButton
                    android:id="@+id/five"
                    android:layout_width="40dp"
                    android:layout_height="35dp"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="10dp"
                    android:layout_weight="1"
                    android:background="@drawable/radio_button_rechange_amount_background"
                    android:button="@null"
                    android:gravity="center_vertical|center_horizontal"
                    android:padding="2dp"
                    android:text="@string/activity_recharge_mount_5"
                    android:textColor="@drawable/textcolor_rechange_amount_background"
                    android:textSize="@dimen/medium_text_size" />

                <RadioButton
                    android:id="@+id/ten"
                    android:layout_width="40dp"
                    android:layout_height="35dp"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="10dp"
                    android:layout_weight="1"
                    android:background="@drawable/radio_button_rechange_amount_background"
                    android:button="@null"
                    android:gravity="center_vertical|center_horizontal"
                    android:padding="2dp"
                    android:text="@string/activity_recharge_mount_10"
                    android:textColor="@drawable/textcolor_rechange_amount_background"
                    android:textSize="@dimen/medium_text_size" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="10dp"
                android:gravity="center_vertical|center_horizontal" >

                <RadioButton
                    android:id="@+id/twenty"
                    android:layout_width="40dp"
                    android:layout_height="35dp"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="10dp"
                    android:layout_weight="1"
                    android:background="@drawable/radio_button_rechange_amount_background"
                    android:button="@null"
                    android:gravity="center_vertical|center_horizontal"
                    android:padding="2dp"
                    android:text="@string/activity_recharge_mount_20"
                    android:textColor="@drawable/textcolor_rechange_amount_background"
                    android:textSize="@dimen/medium_text_size" />

                <RadioButton
                    android:id="@+id/fifty"
                    android:layout_width="40dp"
                    android:layout_height="35dp"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="10dp"
                    android:layout_weight="1"
                    android:background="@drawable/radio_button_rechange_amount_background"
                    android:button="@null"
                    android:gravity="center_vertical|center_horizontal"
                    android:padding="2dp"
                    android:text="@string/activity_recharge_mount_50"
                    android:textColor="@drawable/textcolor_rechange_amount_background"
                    android:textSize="@dimen/medium_text_size" />

                <RadioButton
                    android:id="@+id/one_hundred"
                    android:layout_width="40dp"
                    android:layout_height="35dp"
                    android:layout_marginLeft="10dp"
                    android:layout_marginRight="10dp"
                    android:layout_weight="1"
                    android:background="@drawable/radio_button_rechange_amount_background"
                    android:button="@null"
                    android:gravity="center_vertical|center_horizontal"
                    android:padding="2dp"
                    android:text="@string/activity_recharge_mount_100"
                    android:textColor="@drawable/textcolor_rechange_amount_background"
                    android:textSize="@dimen/medium_text_size" />
            </LinearLayout>
        </com.sydb.common.widgets.CustomNestRadioGroup>

修改後的CustomNestRadioGroup源碼:

package com.sydb.common.widgets;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.LinearLayout;

//這個類是源碼RadioGroup複製出來,增加了遞歸查找子控件RadioButton
public class CustomNestRadioGroup extends LinearLayout {
    // holds the checked id; the selection is empty by default
    private int mCheckedId = -1;

    // tracks children radio buttons checked state
    private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;

    // when true, mOnCheckedChangeListener discards events
    private boolean mProtectFromCheckedChange = false;

    private OnCheckedChangeListener mOnCheckedChangeListener;

    private PassThroughHierarchyChangeListener mPassThroughListener;

    /**
     * {@inheritDoc}
     */
    public CustomNestRadioGroup(Context context) {
        super(context);
        init();
    }

    /**
     * {@inheritDoc}
     */
    public CustomNestRadioGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mCheckedId = View.NO_ID;
        // setOrientation(VERTICAL); //可以在這裏設置線性佈局方向
        mChildOnCheckedChangeListener = new CheckedStateTracker();
        mPassThroughListener = new PassThroughHierarchyChangeListener();
        super.setOnHierarchyChangeListener(mPassThroughListener);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
        // the user listener is delegated to our pass-through listener
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        // checks the appropriate radio button as requested in the XML file
        if (mCheckedId != View.NO_ID) {
            mProtectFromCheckedChange = true;
            setCheckedStateForView(mCheckedId, true);
            mProtectFromCheckedChange = false;
            setCheckedId(mCheckedId);
        }
    }

    /** 遞歸查找具有選中屬性的子控件 */
    private static List<CompoundButton> findCheckedView(View child) {
        List<CompoundButton> list = new ArrayList<CompoundButton>();
        if (child instanceof CompoundButton) {
            list.add((CompoundButton) child);
            return list;
        }
        if (child instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) child;
            for (int i = 0, j = group.getChildCount(); i < j; i++) {
                List<CompoundButton> list2 = findCheckedView(group.getChildAt(i));
                list.addAll(list2);
            }
        }
        return list;// 沒有找到
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        final List<CompoundButton> views = findCheckedView(child);
        for (CompoundButton view : views) {
            if (view != null) {
                if (view.isChecked()) {
                    mProtectFromCheckedChange = true;
                    if (mCheckedId != -1) {
                        setCheckedStateForView(mCheckedId, false);
                    }
                    mProtectFromCheckedChange = false;
                    setCheckedId(view.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(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(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 CompoundButton) {
            ((CompoundButton) 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
     * @see #check(int)
     * @see #clearCheck()
     * @attr ref android.R.styleable#CustomNestRadioGroup_checkedButton
     */
    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>
     * 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 LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new CustomNestRadioGroup.LayoutParams(getContext(), attrs);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof CustomNestRadioGroup.LayoutParams;
    }

    @Override
    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    /**
     * <p>
     * This set of layout parameters defaults the width and the height of the
     * children to {@link #WRAP_CONTENT} when they are not specified in the XML
     * file. Otherwise, this class ussed the value read from the XML file.
     * </p>
     * <p>
     * See {@link android.R.styleable#LinearLayout_Layout LinearLayout
     * Attributes} for a list of all child view attributes that this class
     * supports.
     * </p>
     */
    public static class LayoutParams extends LinearLayout.LayoutParams {
        /**
         * {@inheritDoc}
         */
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(int w, int h) {
            super(w, h);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(int w, int h, float initWeight) {
            super(w, h, initWeight);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(ViewGroup.LayoutParams p) {
            super(p);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        /**
         * <p>
         * Fixes the child's width to
         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the
         * child's height to
         * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} when not
         * specified in the XML file.
         * </p>
         * 
         * @param a
         *            the styled attributes set
         * @param widthAttr
         *            the width attribute to fetch
         * @param heightAttr
         *            the height attribute to fetch
         */
        @Override
        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {

            if (a.hasValue(widthAttr)) {
                width = a.getLayoutDimension(widthAttr, "layout_width");
            } else {
                width = WRAP_CONTENT;
            }

            if (a.hasValue(heightAttr)) {
                height = a.getLayoutDimension(heightAttr, "layout_height");
            } else {
                height = WRAP_CONTENT;
            }
        }
    }

    /**
     * <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(CustomNestRadioGroup group, int checkedId);
    }

    private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
        public void onCheckedChanged(CompoundButton 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);
        }
    }

    /**
     * <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}
         */
        // @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
        public void onChildViewAdded(View parent, View child) {
            if (parent == CustomNestRadioGroup.this) {
                List<CompoundButton> views = findCheckedView(child);// 查找子控件
                for (CompoundButton view : views) {
                    if (view != null) {
                        /*
                         * int id = view.getId(); // generates an id if it's
                         * missing if (id == View.NO_ID && Build.VERSION.SDK_INT
                         * >= Build.VERSION_CODES.JELLY_BEAN_MR1) { id =
                         * View.generateViewId(); view.setId(id); }
                         */
                        view.setOnCheckedChangeListener(mChildOnCheckedChangeListener);
                    }
                }
            }

            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
            }
        }

        /**
         * {@inheritDoc}
         */
        public void onChildViewRemoved(View parent, View child) {
            if (parent == CustomNestRadioGroup.this) {
                List<CompoundButton> views = findCheckedView(child);// 查找子控件
                for (CompoundButton view : views) {
                    if (view != null) {
                        view.setOnCheckedChangeListener(null);
                    }
                }
            }

            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章