天天看點

【Android自定義控件】支援多層嵌套RadioButton的RadioGroup

前言

非常喜歡用radiobutton+radiogroup做tabs,能自動處理選中等效果,但是自帶的radiogroup不支援嵌套radiobutton(從源碼可看出僅僅是判斷子控件是不是radiobutton),本文參考radiogroup修改了一個支援嵌套compoundbutton的控件,非常實用。 

聲明

歡迎轉載,但請保留文章原始出處:) 

部落格園:http://www.cnblogs.com

農民伯伯: http://over140.cnblogs.com 

正文

【Android自定義控件】支援多層嵌套RadioButton的RadioGroup

/**

 * 支援嵌套compoundbutton的nestradiogroup

 * 

 * @author 農民伯伯 http://www.cnblogs.com/over140/

 *

 */

public class nestradiogroup 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 nestradiogroup(context context) {

        super(context);

        init();

    }

    public nestradiogroup(context context, attributeset attrs) {

        super(context, attrs);

    private void init() {

        mcheckedid = view.no_id;

        setorientation(horizontal);

        mchildoncheckedchangelistener = new checkedstatetracker();

        mpassthroughlistener = new passthroughhierarchychangelistener();

        super.setonhierarchychangelistener(mpassthroughlistener);

    @override

    public void setonhierarchychangelistener(onhierarchychangelistener listener) {

        // the user listener is delegated to our pass-through listener

        mpassthroughlistener.monhierarchychangelistener = listener;

    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 compoundbutton findcheckedview(view child) {

        if (child instanceof compoundbutton)

            return (compoundbutton) child;

        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;//沒有找到

    public void addview(view child, int index, viewgroup.layoutparams params) {

        final compoundbutton view = findcheckedview(child);

        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)

     * @attr ref android.r.styleable#nestradiogroup_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>

    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;

    public layoutparams generatelayoutparams(attributeset attrs) {

        return new nestradiogroup.layoutparams(getcontext(), attrs);

    protected boolean checklayoutparams(viewgroup.layoutparams p) {

        return p instanceof nestradiogroup.layoutparams;

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

        public layoutparams(int w, int h) {

            super(w, h);

        public layoutparams(int w, int h, float initweight) {

            super(w, h, initweight);

        public layoutparams(viewgroup.layoutparams p) {

            super(p);

        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");

                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(nestradiogroup group, int checkedid);

    private class checkedstatetracker implements compoundbutton.oncheckedchangelistener {

        public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) {

            // prevents from infinite recursion

            if (mprotectfromcheckedchange) {

                return;

            if (mcheckedid != -1) {

                setcheckedstateforview(mcheckedid, 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;

        @targetapi(build.version_codes.jelly_bean_mr1)

    public void onchildviewadded(view parent, view child) {

            if (parent == nestradiogroup.this) {

                compoundbutton view = findcheckedview(child);//查找子控件

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

        public void onchildviewremoved(view parent, view child) {

                    view.setoncheckedchangelistener(null);

                monhierarchychangelistener.onchildviewremoved(parent, child);

【Android自定義控件】支援多層嵌套RadioButton的RadioGroup

}  

代碼說明

代碼主要是仿照radiogroup改寫,主要是findcheckedview方法遞歸查找具有選中屬性的子控件。

用法

【Android自定義控件】支援多層嵌套RadioButton的RadioGroup

        <com.xxx.ui.view.nestradiogroup

            android:id="@+id/main_radio"

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:gravity="center"

            android:orientation="horizontal" >

            <radiobutton

                android:id="@+id/radio_button0"

                android:layout_width="0dip"

                android:layout_height="wrap_content"

                android:layout_weight="1.0"

                android:checked="true"

                android:text="@string/bottom_feed" />

                android:id="@+id/radio_button1"

                android:text="@string/bottom_square" />

            <relativelayout

                android:layout_height="fill_parent"

                android:orientation="horizontal" >

                <radiobutton

                    android:id="@+id/radio_button2"

                    style="@style/title_tab_text_style"

                    android:layout_width="wrap_content"

                    android:layout_height="wrap_content"

                    android:layout_centerinparent="true"

                    android:text="@string/bottom_message" />

                <imageview

                    android:id="@+id/new_message_tips"

                    android:layout_margintop="10dip"

                    android:layout_torightof="@+id/radio_button2"

                    android:src="@drawable/news_tips_red" />

            </relativelayout>

        </com.xxx.ui.view.nestradiogroup>

【Android自定義控件】支援多層嵌套RadioButton的RadioGroup

  代碼說明 

1、實作非常常見的tabs效果,結合viewpager來使用,new_message_tips可以是一個類似微信右上角的小紅圈,用來提醒有新的消息。 

2、view.generateviewid需要4.2以上才能使用,是以最好自己設定id

轉自:http://www.cnblogs.com/over140/p/3795877.html

繼續閱讀