How to group a 3x3 grid of radio buttons?

This uses a custom GridLayout with the RadioGroup functionality. Thanks to Saikrishnan Ranganathan for saiaspire/RadioGridGroup

<com.sample.RadioGridGroup
        xmlns:grid="http://schemas.android.com/apk/res-auto"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        grid:columnCount="3"
        grid:useDefaultMargins="true">

        <android.support.v7.widget.AppCompatRadioButton
            android:checked="true"
            android:text="Text1"
            grid:layout_columnWeight="1"/>

        <android.support.v7.widget.AppCompatRadioButton
            android:text="Text2"
            grid:layout_columnWeight="1"/>

        <android.support.v7.widget.AppCompatRadioButton
            android:text="Text3"
            grid:layout_columnWeight="1"/>

        <android.support.v7.widget.AppCompatRadioButton
            android:text="Text4"
            grid:layout_columnWeight="1"/>

        <android.support.v7.widget.AppCompatRadioButton
            android:text="Text5"
            grid:layout_columnWeight="1"/>

        <android.support.v7.widget.AppCompatRadioButton
            android:text="Text6"
            grid:layout_columnWeight="1"/>

        <android.support.v7.widget.AppCompatRadioButton
            android:text="Text7"
            grid:layout_columnWeight="1"/>

        <android.support.v7.widget.AppCompatRadioButton
            android:text="Text8"
            grid:layout_columnWeight="1"/>

        <android.support.v7.widget.AppCompatRadioButton
            android:text="Text9"
            grid:layout_columnWeight="1"/>

    </com.sample.RadioGridGroup>

After above https://stackoverflow.com/a/2383978/5567009 answer I got another solution for this question, I added some other functionality like, to save the state of the group and also functionality to clear the check functionality like in radio group.

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IdRes;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RadioButton;
import android.widget.TableLayout;
import android.widget.TableRow;

public class RadioGridGroup extends TableLayout implements View.OnClickListener {

    private static final String TAG = "ToggleButtonGroupTableLayout";
    private int checkedButtonID = -1;

    /**
     * @param context
     */
    public RadioGridGroup(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    /**
     * @param context
     * @param attrs
     */
    public RadioGridGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    public void onClick(View v) {
        if (v instanceof RadioButton) {
            int id = v.getId();
            check(id);
        }
    }

    private void setCheckedStateForView(int viewId, boolean checked) {
        View checkedView = findViewById(viewId);
        if (checkedView != null && checkedView instanceof RadioButton) {
            ((RadioButton) checkedView).setChecked(checked);
        }
    }

    /* (non-Javadoc)
     * @see android.widget.TableLayout#addView(android.view.View, int, android.view.ViewGroup.LayoutParams)
     */
    @Override
    public void addView(View child, int index,
                        android.view.ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        setChildrenOnClickListener((TableRow) child);
    }


    /* (non-Javadoc)
     * @see android.widget.TableLayout#addView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    @Override
    public void addView(View child, android.view.ViewGroup.LayoutParams params) {
        super.addView(child, params);
        setChildrenOnClickListener((TableRow) child);
    }


    private void setChildrenOnClickListener(TableRow tr) {
        final int c = tr.getChildCount();
        for (int i = 0; i < c; i++) {
            final View v = tr.getChildAt(i);
            if (v instanceof RadioButton) {
                v.setOnClickListener(this);
            }
        }
    }


    /**
     * @return the checked button Id
     */
    public int getCheckedRadioButtonId() {
        return checkedButtonID;
    }


    /**
     * Check the id
     *
     * @param id
     */
    public void check(@IdRes int id) {
        // don't even bother
        if (id != -1 && (id == checkedButtonID)) {
            return;
        }
        if (checkedButtonID != -1) {
            setCheckedStateForView(checkedButtonID, false);
        }
        if (id != -1) {
            setCheckedStateForView(id, true);
        }
        setCheckedId(id);
    }

    /**
     * set the checked button Id
     *
     * @param id
     */
    private void setCheckedId(int id) {
        this.checkedButtonID = id;
    }

    public void clearCheck() {
        check(-1);
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        this.checkedButtonID = ss.buttonId;
        setCheckedStateForView(checkedButtonID, true);
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState savedState = new SavedState(superState);
        savedState.buttonId = checkedButtonID;
        return savedState;
    }

    static class SavedState extends BaseSavedState {
        int buttonId;

        /**
         * Constructor used when reading from a parcel. Reads the state of the superclass.
         *
         * @param source
         */
        public SavedState(Parcel source) {
            super(source);
            buttonId = source.readInt();
        }

        /**
         * Constructor called by derived classes when creating their SavedState objects
         *
         * @param superState The state of the superclass of this view
         */
        public SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeInt(buttonId);
        }

        public static final Parcelable.Creator<SavedState> CREATOR =
                new Parcelable.Creator<SavedState>() {
                    public SavedState createFromParcel(Parcel in) {
                        return new SavedState(in);
                    }

                    public SavedState[] newArray(int size) {
                        return new SavedState[size];
                    }
                };
    }
}

and use this in XML as follows

<com.test.customviews.RadioGridGroup xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TableRow android:layout_marginTop="@dimen/preview_five">

        <RadioButton
            android:id="@+id/rad1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button1" />

        <RadioButton
            android:id="@+id/rad2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button2" />
    </TableRow>

    <TableRow android:layout_marginTop="@dimen/preview_five">

        <RadioButton
            android:id="@+id/rad3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button3" />

        <RadioButton
            android:id="@+id/rad4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button4" />
    </TableRow>

    <TableRow android:layout_marginTop="@dimen/preview_five">

        <RadioButton
            android:id="@+id/rad5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button5" />

        <RadioButton
            android:id="@+id/rad6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button6" />
    </TableRow>

    <TableRow android:layout_marginTop="@dimen/preview_five">

        <RadioButton
            android:id="@+id/rad7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button7" />

        <RadioButton
            android:id="@+id/rad8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button8" />
    </TableRow>
</com.test.customviews.RadioGridGroup>

For any other improvements, please comment.


Actually it's not that hard if you subclass TableLayout like in this example

/**
 * 
 */
package com.codtech.android.view;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.RadioButton;
import android.widget.TableLayout;
import android.widget.TableRow;

/**
 * @author diego
 *
 */
public class ToggleButtonGroupTableLayout extends TableLayout  implements OnClickListener {

    private static final String TAG = "ToggleButtonGroupTableLayout";
    private RadioButton activeRadioButton;

    /** 
     * @param context
     */
    public ToggleButtonGroupTableLayout(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    /**
     * @param context
     * @param attrs
     */
    public ToggleButtonGroupTableLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    public void onClick(View v) {
        final RadioButton rb = (RadioButton) v;
        if ( activeRadioButton != null ) {
            activeRadioButton.setChecked(false);
        }
        rb.setChecked(true);
        activeRadioButton = rb;
    }

    /* (non-Javadoc)
     * @see android.widget.TableLayout#addView(android.view.View, int, android.view.ViewGroup.LayoutParams)
     */
    @Override
    public void addView(View child, int index,
            android.view.ViewGroup.LayoutParams params) {
        super.addView(child, index, params);
        setChildrenOnClickListener((TableRow)child);
    }


    /* (non-Javadoc)
     * @see android.widget.TableLayout#addView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    @Override
    public void addView(View child, android.view.ViewGroup.LayoutParams params) {
        super.addView(child, params);
        setChildrenOnClickListener((TableRow)child);
    }


    private void setChildrenOnClickListener(TableRow tr) {
        final int c = tr.getChildCount();
        for (int i=0; i < c; i++) {
            final View v = tr.getChildAt(i);
            if ( v instanceof RadioButton ) {
                v.setOnClickListener(this);
            }
        }
    }

    public int getCheckedRadioButtonId() {
        if ( activeRadioButton != null ) {
            return activeRadioButton.getId();
        }

        return -1;
    }
}

and create a layout like this (of course you need to clean it up but you got the idea)

<?xml version="1.0" encoding="utf-8"?>
<com.codtech.android.view.ToggleButtonGroupTableLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content" android:layout_height="wrap_content"
    android:id="@+id/radGroup1">
    <TableRow>
            <RadioButton android:id="@+id/rad1" android:text="Button1"
                android:layout_width="105px" android:layout_height="wrap_content"
                android:textSize="13px" />
            <RadioButton android:id="@+id/rad2" android:text="Button2"
                android:layout_width="105px" android:textSize="13px"
                android:layout_height="wrap_content" />
            <RadioButton android:id="@+id/rad3" android:text="Button3"
                android:layout_width="105px" android:textSize="13px"
                android:layout_height="wrap_content" />
    </TableRow>
    <TableRow>
            <RadioButton android:id="@+id/rad1" android:text="Button1"
                android:layout_width="105px" android:layout_height="wrap_content"
                android:textSize="13px" />
            <RadioButton android:id="@+id/rad2" android:text="Button2"
                android:layout_width="105px" android:textSize="13px"
                android:layout_height="wrap_content" />
            <RadioButton android:id="@+id/rad3" android:text="Button3"
                android:layout_width="105px" android:textSize="13px"
                android:layout_height="wrap_content" />
    </TableRow>
    <TableRow>
            <RadioButton android:id="@+id/rad1" android:text="Button1"
                android:layout_width="105px" android:layout_height="wrap_content"
                android:textSize="13px" />
            <RadioButton android:id="@+id/rad2" android:text="Button2"
                android:layout_width="105px" android:textSize="13px"
                android:layout_height="wrap_content" />
            <RadioButton android:id="@+id/rad3" android:text="Button3"
                android:layout_width="105px" android:textSize="13px"
                android:layout_height="wrap_content" />
    </TableRow>
</com.codtech.android.view.ToggleButtonGroupTableLayout>

I for one would go for nested RadioGroups. The Root RadioGroup will have a Vertical orientation and its children will be three RadioGroups with Horizontal orientation.

<RadioGroup
    android:orientation="vertical">
    <RadioGroup
        android:id="@+id/rg_1"
        android:orientation="horizontal">
        <RadioButton />
        <RadioButton />
        <RadioButton />
    </RadioGroup>
    <RadioGroup
        android:id="@id/rg_2"
        android:orientation="horizontal">
        <RadioButton />
        <RadioButton />
        <RadioButton />
    </RadioGroup>
    <RadioGroup
        android:id="@+id/rg_3"
        android:orientation="horizontal">
        <RadioButton />
        <RadioButton />
        <RadioButton />
    </RadioGroup>
</RadioGroup>

Each of the chield RadioGroups will have an ID which will be called by a RadioGroup object inside the java validation method. Like this:

RadioGroup rg_1 = (RadioGroup) findViewById(R.id.rg_1);
RadioGroup rg_2 = (RadioGroup) findViewById(R.id.rg_2);
RadioGroup rg_3 = (RadioGroup) findViewById(R.id.rg_3);

Now simply by using clearCheck() inside the switch case you can clear the check of the other two RadioGroups. Like this:

case R.id.radioButton_1:
            if (checked) {
                rg_2.clearCheck();
                rg_3.clearCheck();
            }
            break;

Tags:

Java

Xml

Android