RadioGroup with two columns which have ten RadioButtons
Asked Answered
E

13

30

I have a RadioGroup and I want to align buttons next to each other in two columns and five rows and I am unable to achieve it. Things I have tried:

  1. RelativeLayout -> Outside RadioGroup -> Inside RadioGroup. All RadioButtons are selected, but I want only one to be selected.
  2. RadioGroup : orientation
  3. Span, stretchcolumns
  4. TableRow
  5. TableLayout

Please let me know how create one RadioGroup and have two columns and many RadioButtons within.

Epithelium answered 3/5, 2012 at 5:23 Comment(3)
try the method in this post.Hydrothermal
Thanks Candy,Luksprog has a working solution.Thanks againEpithelium
Try this library github.com/mrHerintsoaHasina/flextoolsImpetrate
B
53

You can simulate that RadioGroup to make it look like you have only one. For example you have rg1 and rg2(RadioGroups with orientation set to vertical(the two columns)). To setup those RadioGroups:

rg1 = (RadioGroup) findViewById(R.id.radioGroup1);
rg2 = (RadioGroup) findViewById(R.id.radioGroup2);
rg1.clearCheck(); // this is so we can start fresh, with no selection on both RadioGroups
rg2.clearCheck();
rg1.setOnCheckedChangeListener(listener1);
rg2.setOnCheckedChangeListener(listener2);

To select only one RadioButton in those RadioGroups the listeners above will be:

private OnCheckedChangeListener listener1 = new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            if (checkedId != -1) {
                rg2.setOnCheckedChangeListener(null); // remove the listener before clearing so we don't throw that stackoverflow exception(like Vladimir Volodin pointed out)
                rg2.clearCheck(); // clear the second RadioGroup!
                rg2.setOnCheckedChangeListener(listener2); //reset the listener
                Log.e("XXX2", "do the work");
            }
        }
    };

    private OnCheckedChangeListener listener2 = new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            if (checkedId != -1) {
                rg1.setOnCheckedChangeListener(null);
                rg1.clearCheck();
                rg1.setOnCheckedChangeListener(listener1);
                Log.e("XXX2", "do the work");
            }
        }
    };

To get the checked RadioButton from the RadioGroups you could do:

int chkId1 = rg1.getCheckedRadioButtonId();
int chkId2 = rg2.getCheckedRadioButtonId();
int realCheck = chkId1 == -1 ? chkId2 : chkId1;

If you use the check() method of the RadioGroup you have to remember to call clearCheck() on the other Radiogroup.

Bergerac answered 3/5, 2012 at 5:49 Comment(10)
Thaaks Luksprog.Awesome solution.Epithelium
this results in stack overflow (Gradate
@VladimirVolodin I doubt that the simple code from my answer throws a stackoverflow exception. If you have problems you should post a question that has more details about your own setup.Bergerac
No, my setup is just as simple as yours. The problem is that clearCheck() always results in a call to onCheckedChangeListener().Gradate
@VladimirVolodin Thanks for pointing that out. My previous code does throw a stackoverflow exception. I don't know why the user who asked the question and accepted my answer didn't point this out. Anyway, check my improved answer, it should be ok now.Bergerac
Should work, I fixed it myself using just a boolean flag instead of unsetting the listener.Gradate
Ridiculous that we have to go through all this just so we can have 2+ columns of radiobuttons.Pedant
beginners question: Where do I put the snippet where the 2 listeners are created? I have my code within the 'onClick' code and in there I launch a popUp window, so I'm quite sure where the listener1/listener2 snippet belongs.Ankle
@Ankle The listener1 and listener2 in the example are defined as two fields in the class(the activity). You could define them as fields in the class containing the onClick code.Bergerac
This is unfortunately not a good solution for accessibility since the TalkBack screenreader. You may want to use the answer for this two-row radiogroup #17259073Armorial
I
9

The RadioGroup is extended from LinearLayout.

the linearlayout can not do it, so RadioGroup can not do it.

Why not implement it self.

Use RelativeLayout to layout the child view. And record the state of the child view. use setLevel to control the states.

Good luck for you!.

Indispose answered 3/5, 2012 at 5:29 Comment(0)
L
4

If the layout is not complicated, best way is to use Single RelativeLayout instead of multiple Linear Layouts.

Below is the code with 2 rows. First row has 3 columns. Second row one column.

            <RadioGroup
                android:id="@+id/radio_group"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginBottom="4dp"
                android:layout_marginTop="4dp"
                android:orientation="vertical">

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content">

                    <android.support.v7.widget.AppCompatRadioButton
                        android:id="@+id/r1c1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignParentLeft="true"
                        android:layout_alignParentStart="true"
                        android:layout_alignParentTop="true"
                        android:layout_marginRight="8dp"
                        android:gravity="center"
                        android:text="Row 1 Column1" />

                    <android.support.v7.widget.AppCompatRadioButton
                        android:id="@+id/r2c1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_below="@id/r1c1"
                        android:layout_gravity="left|center_vertical"
                        android:layout_marginRight="8dp"
                        android:layout_weight="1"
                        android:gravity="left|center_vertical"
                        android:text="Row 2 Column 1" />

                    <android.support.v7.widget.AppCompatRadioButton
                        android:id="@+id/r1c2"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginRight="8dp"
                        android:layout_toRightOf="@id/r1c1"
                        android:gravity="center"
                        android:text="Row 1 Column 2"/>

                    <android.support.v7.widget.AppCompatRadioButton
                        android:id="@+id/r1c3"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginRight="8dp"
                        android:layout_toRightOf="@id/r1c2"
                        android:gravity="center"
                        android:text="Row 1 Column 3" />
                </RelativeLayout>
            </RadioGroup>
Luisaluise answered 9/8, 2016 at 14:10 Comment(2)
It will not work. If I check any radiobutton then other radiobutton doesn't unchecked automatically.Glutinous
Did not work. It has allowed the positions of Radio Buttons adjustable. But on selection, the selectionid is returning -1 (none is checked). It is allowing all buttons checked, even though they belong to the same radio group.Resound
S
2

Make 2 RadioGroup in the xml file using LinearLayout each having 5 RadioButton and using layout_weight property place them side by side on the screen. Then create listener for these radio groups as shown below:

rg1 = (RadioGroup) findViewById(R.id.radiogroup1);
rg2 = (RadioGroup) findViewById(R.id.radiogroup2);
rg1.clearCheck();//this is so we can start fresh, with no selection on both RadioGroups
rg2.clearCheck();
rg1.setOnCheckedChangeListener(new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            // TODO Auto-generated method stub
            if (checkedId != -1) {
                fun2();
            }
        }
    });

    rg2.setOnCheckedChangeListener(new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            // TODO Auto-generated method stub
            if (checkedId != -1) {
                fun1();
            }
        }
    });

And fun1() & fun2() will be defined as shown below:

public void fun1() {
     rg1.setOnCheckedChangeListener(null);
     rg1.clearCheck();
     rg1.setOnCheckedChangeListener(new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            fun2();
            Log.v("Inside fun1","fun2");
        }
    });
}

public void fun2() {
     rg2.setOnCheckedChangeListener(null);
     rg2.clearCheck();
     rg2.setOnCheckedChangeListener(new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            // TODO Auto-generated method stub
            fun1();
            Log.v("Inside fun2","fun1");

        }
    });
}
Settling answered 14/8, 2014 at 10:2 Comment(1)
Does not work! rg1 on click listener is not hit when changing radio button in that group. First need to hit a radio button in rg2 before even one listener gets hit.Ringhals
O
1

I had to do the same thing and wound up combining a TableLayout and a RadioButtonGroup. I added the radio buttons dynamically to TableRows. Here is the class:

public class RadioGroupColumns extends TableLayout implements OnClickListener {

private static final String TAG = "RadioGroupColumns";
private RadioButton activeRadioButton;
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;

public RadioGroupColumns(Context context) {
    super(context);
    setOrientation(VERTICAL);
    init();
}

public RadioGroupColumns(Context context, AttributeSet attrs) {
            super(context, attrs);
            Resources res = Resources.getSystem();
            int value = 0;
            // retrieve selected radio button as requested by the user in the
            // XML layout file

            TypedArray attributes = null;
            try {
                attributes = context.obtainStyledAttributes(attrs, getAttributes(context), R.attr.radioButtonStyle, 0);
                value = attributes.getResourceId(getAttribute(context), View.NO_ID);
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                Log.d("Exception RadioGroupColumns Construct",e.toString());
                e.printStackTrace();
            }
            catch (ClassNotFoundException e) { 
                Log.d("Exception RadioGroupColumns Construct",e.toString());
                e.printStackTrace();
            }  


            if (value != View.NO_ID) {
                mCheckedId = value;
            }

            //hardcode it to vertical
            //final int index = attributes.getInt(com.android.internal.R.styleable.RadioGroup_orientation, VERTICAL);
            //setOrientation(index);

            attributes.recycle();

            setOrientation(VERTICAL);
            init();
}

@Override
public void onClick(View v) {
    if (v instanceof TableRow) {
        TableRow row = (TableRow)v;
        for (int j=0;j<row.getChildCount();j++) { 
            if (RadioButton.class.isAssignableFrom(row.getChildAt(j).getClass())) {

                ((RadioButton) row.getChildAt(j)).setChecked(true);
                activeRadioButton = (RadioButton) row.getChildAt(j);
            }
        }
    }
    else { 
        final RadioButton rb = (RadioButton) v;

        if (activeRadioButton != null) {
            activeRadioButton.setChecked(false);
        }
        rb.setChecked(true);
        activeRadioButton = rb;
    }

}

private void init() {
    mChildOnCheckedChangeListener = new CheckedStateTracker();
    mPassThroughListener = new PassThroughHierarchyChangeListener();
    super.setOnHierarchyChangeListener(mPassThroughListener);
}

private int getAttribute(Context con) throws ClassNotFoundException, IllegalArgumentException, IllegalAccessException { 
//use reflect to get styleable class.
Field[] alFields = null; 
ArrayList<Integer> alInts = new ArrayList<Integer>();
int R_ID = 0;

for (Class c : android.R.class.getClasses()) { 
     if (c.getName().indexOf("styleable") >= 0) { 
         alFields = Class.forName( con.getPackageName() + ".R$styleable" ).getFields();

     } 
 }
for (Field f : alFields) { 
    Log.d("field name",f.getName());
    if (f.getName().equals("RadioGroup_checkedButton")) { 
        int[] ret = (int[])f.get(null);
        R_ID = ret[0];
    }

}
return R_ID;


}

//gets all RadioGroup R,android.internal.styleable.RadioGroup values
private int[] getAttributes(Context con) throws IllegalAccessException, ClassNotFoundException { 
    //use reflect to get styleable class.
    Field[] alFields = null; 
    ArrayList<Integer> alInts = new ArrayList<Integer>();
    int[] ints = null;
    int count = 0;
    try {
        for (Class c : android.R.class.getClasses()) { 
             if (c.getName().indexOf("styleable") >= 0) { 
                 Log.d("get Class Name Outer", c.getName());
                //use reflection to access the resource class
                 alFields = Class.forName( con.getPackageName() + ".R$styleable" ).getFields();          
             } 

         }
         if (alFields != null) 
             {
                Log.d("field numbers size", String.valueOf(alFields.length));

                for (Field field : alFields) { 

                 Class<?> targetType = field.getType();
                 Log.d("field type", field.getType().toString());
                 if (targetType.equals(Integer.TYPE) && targetType.isPrimitive()) { 
                     //alInts.add((Integer)field);
                     Object objectValue = (Integer)field.getInt(null);
                     //Object objectValue = (Integer)targetType.newInstance(); 
                     alInts.add((Integer)objectValue);
                     count++;
                 }
                 ints = new int[count];
                 for (int i=0;i<alInts.size();i++) { 
                     ints[i] = alInts.get(i);
                 }
             }

         }
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    catch (ClassNotFoundException e) { 
        e.printStackTrace();
    }
    return ints;


    }

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);
    }
    activeRadioButton = (RadioButton) findViewById(id);
    activeRadioButton.setChecked(true);
    setCheckedId(id);
}

public void setOnCheckedChangeListener(RadioGroupColumns.OnCheckedChangeListener onCheckedChangeListener) {
    mOnCheckedChangeListener = (OnCheckedChangeListener) onCheckedChangeListener;
}

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 (!RadioButton.class.isAssignableFrom(checkedView.getClass()) && checkedView != null) { 
        TableRow row = (TableRow) checkedView;
        for (int j=0;j<row.getChildCount();j++) { 
            RadioButton button = (RadioButton) row.getChildAt(j);
            if (button.isChecked() && button != null) { 
                button.setChecked(checked);
            }
        }
    }
    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);
        }
    }
}

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

    return -1;
}

public interface OnCheckedChangeListener {

    public void onCheckedChanged(RadioGroupColumns 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);
    }
}

private class PassThroughHierarchyChangeListener implements android.view.ViewGroup.OnHierarchyChangeListener {
    private android.view.ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;

    /**
     * {@inheritDoc}
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public void onChildViewAdded(View parent, View child) {
        if (parent == RadioGroupColumns.this
                && child instanceof RadioButton) {
            int id = child.getId();
            // generates an id if it's missing
            if (id == View.NO_ID) {
                id = View.generateViewId();
                child.setId(id);
            }
            ((RadioButton) child).setOnCheckedChangeListener((com.assistek.ediary.RadioButton.OnCheckedChangeListener) mChildOnCheckedChangeListener);
        }

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

    /**
     * {@inheritDoc}
     */
    public void onChildViewRemoved(View parent, View child) {
        if (parent == RadioGroupColumns.this
                && child instanceof RadioButton) {
            ((RadioButton) child).setOnCheckedChangeListener(null);
        }

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

 }

Here is the code adding the radio buttons to the extended radiogroup:

    private void setupRadioButtonAnswers() {
    ArrayList<HolderAnswer> listAnswers = GlobalVars.questionHolders[GlobalVars.arrayRowNumber]
            .getListAnswers();
    ArrayList<ArrayList<HolderAnswer>> listAnswersSorted = new ArrayList<ArrayList<HolderAnswer>>();
    ArrayList<TableRow> alTableRows = new ArrayList<TableRow>();

    int NumberInColumns = (int) Math.floor(listAnswers.size() / NUMBER_OF_COLUMNS);
    // make higher number of answers on the right
    if (listAnswers.size() % NUMBER_OF_COLUMNS > 0)
        NumberInColumns++;

    for (int i = 0; i < NumberInColumns; i++) {
        TableRow row = new TableRow(this);
        TableRow.LayoutParams lp = new TableRow.LayoutParams(
                TableRow.LayoutParams.WRAP_CONTENT);
        row.setLayoutParams(lp);
        alTableRows.add(row);
    }

    int count = 0;

    // sort by row
    /*
     * a[0] = "Question 1"
       a[1] = "Question 2"
       a[2] = "Question 3"
       a[3] = "Question 4"
       a[4] = "Question 5"
       a[5] = "Question 6"
       a[6] = "Question 7"

       sorted to:

       a[0] = "Question 1"   a[1] = "Question 5"
       a[2] = "Question 2"   a[3] = "Question 6"
       a[4] = "Question 3"   a[5] = "Question 7"
       a[6] = "Question 4"
     */

    // initialize the ArrayLists in listAnswersSorted
    int numRows = listAnswers.size() / NUMBER_OF_COLUMNS + 1;
    for (int i = 0; i < numRows; i += 1) {
        listAnswersSorted.add(new ArrayList<HolderAnswer>());
    }

    // calculate column index where the "step" happens
    int step = listAnswers.size() % NUMBER_OF_COLUMNS;

    // loop through and add elements to listAnswersSorted
    int index = 0;
    int row = 0;
    int col = 0;
    while (index < listAnswers.size()) {
        listAnswersSorted.get(row).add(listAnswers.get(index));

        int rows = col < step ? numRows : numRows - 1;
        row += 1;
        if (row == rows) {
        row = 0;
        col += 1;
        }
        index += 1;
    }


    row = 0;
    int columncount = 1;
    for (ArrayList<HolderAnswer> sortedArrayList : listAnswersSorted) {
        for (HolderAnswer answer : sortedArrayList) {

            final RadioButton button = new RadioButton(this);
            button.setTag(answer.getRecID());
            button.setId(GlobalVars.getLatestId());
            button.setTextColor(Color.BLACK);
            GlobalVars.setupText(con, button, answer.getTextID());

            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    button.setEnabled(false);
                    handlerReenableView.sendEmptyMessageDelayed(button.getId(), 1000);

                    button.setChecked(true);
                    radioGroup.check(button.getId());
                }
            });


            button.setLayoutParams(new TableRow.LayoutParams(columncount));
            alTableRows.get(row).addView(button);

            if (columncount==NUMBER_OF_COLUMNS) { 
                columncount = 1;
                radioGroup.addView(alTableRows.get(row));
                alTableRows.get(row).setOnClickListener(new View.OnClickListener() {

                    @Override
                    public void onClick(View v) {

                        for(int k=0;k<((TableRow) v).getChildCount();k++) { 
                            TableRow row = (TableRow) v;
                            for (int l=0;l<row.getChildCount();l++) { 
                                RadioButton tableButton =  (RadioButton) row.getChildAt(l);
                                if (tableButton.isChecked) { 
                                    radioGroup.check(tableButton.getId());
                                }
                            }
                        }
                    }

                });

            }

            else { 
                columncount++;
            }
            //if (row=NumberInColumns)
            count++;

        }

        if (count == listAnswers.size()) { 
            radioGroup.addView(alTableRows.get(row));
        }
        row++;

    }



    radioGroup.setOnCheckedChangeListener(new RadioGroupColumns.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(RadioGroupColumns group, int checkedId) {
                    Log.d("We're here",String.valueOf(checkedId));
                    if (checkedId == -1) {
                        for (int i = 0; i < radioGroup.getChildCount(); i++) {
                            TableRow row = (TableRow)radioGroup.getChildAt(i); 
                            for (int j=0;j<row.getChildCount();j++) { 
                                if (RadioButton.class.isAssignableFrom(row.getChildAt(j).getClass())) {
                                    ((RadioButton) row.getChildAt(j)).setChecked(false);
                                }
                            }
                        }
                    } else {
                        for (int i = 0; i < radioGroup.getChildCount(); i++) {
                            TableRow row = (TableRow)radioGroup.getChildAt(i); 
                            for (int j=0;j<row.getChildCount();j++) { 
                                if (RadioButton.class.isAssignableFrom(row.getChildAt(j).getClass()) 
                                        && row.getChildAt(j).getId() != -1) {
                                    ((RadioButton) row.getChildAt(j)).setChecked(false);
                                }
                            }

                        }

                        RadioButton checkedRadioButton = (RadioButton) radioGroup.findViewById(checkedId);
                        checkedRadioButton.setChecked(true);
                        Log.d("checkedID onchecked Change()", String.valueOf(radioGroup.getCheckedRadioButtonId()));
                    }
                }

            });

}
Ozmo answered 17/3, 2014 at 21:22 Comment(0)
L
1

UPDATE: I updated the code since I posted it here. The updated one is in this link: https://github.com/Gavras/MultiLineRadioGroup/blob/master/app/src/main/java/com/whygraphics/multilineradiogroup/MultiLineRadioGroup.java

Something really small I did when I needed multi line radio group

It's a custom view that extends RadioGroup.

you have the ability to choose the maximum buttons you want in a row. It is based on TableLayout so it aligns the buttons too. Everything is documented.

/**
 * Layout that arranges radio buttons in multiple lines.
 * Only one radio button can be checked at the same time.
 * <p>
 * XML Attributes:
 * <p>
 * max_in_row:
 * A non-negative number that represents the maximum radio buttons in a row,
 * 0 for all in one line.
 * <p>
 * radio_buttons:
 * String-array resource reference that represents the texts of the desired radio buttons.
 * <p>
 * default_button:
 * String that represents the text or the index of the radio button to be checked by default.
 * The string should be in the following format:
 * for text: "text:[text-of-button]" where text-of-button is the text of the button to check.
 * for index: "index:[index-of-button]" where index-of-button is the index of the button to check.
 * when the prefix omitted, "text:" inserted implicitly.
 */
public class MultiLineRadioGroup extends RadioGroup {

    private static final String XML_DEFAULT_BUTTON_PREFIX_INDEX = "index:";
    private static final String XML_DEFAULT_BUTTON_PREFIX_TEXT = "text:";

    private static final int DEF_VAL_MAX_IN_ROW = 0;

    private OnCheckedChangeListener mOnCheckedChangeListener;

    private int mMaxInRow;

    // all buttons are stored in table layout
    private TableLayout mTableLayout;

    // list to store all the buttons
    private List<RadioButton> mRadioButtons;

    // the checked button
    private RadioButton checkedButton;

    /**
     * Creates a new MultiLineRadioGroup for the given context.
     *
     * @param context the application environment
     */
    public MultiLineRadioGroup(Context context) {
        super(context);
        init(null);
    }

    /**
     * Creates a new MultiLineRadioGroup for the given context
     * and with the specified set attributes.
     *
     * @param context the application environment
     * @param attrs   a collection of attributes
     */
    public MultiLineRadioGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    // initializes the layout
    private void init(AttributeSet attrs) {
        mRadioButtons = new ArrayList<>();

        mTableLayout = getTableLayout();
        addView(mTableLayout);

        if (attrs != null)
            initAttrs(attrs);
    }

    // initializes the layout with the specified attributes
    private void initAttrs(AttributeSet attrs) {
        TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(
                attrs, R.styleable.multi_line_radio_group,
                0, 0);
        try {
            // gets and sets the max in row.
            setMaxInRow(typedArray.getInt(R.styleable.multi_line_radio_group_max_in_row,
                    DEF_VAL_MAX_IN_ROW));

            // gets and adds the starting buttons
            CharSequence[] radioButtonStrings = typedArray.getTextArray(
                    R.styleable.multi_line_radio_group_radio_buttons);
            addButtons(radioButtonStrings);

            // gets the default button and checks it if presents.
            String string = typedArray.getString(R.styleable.multi_line_radio_group_default_button);
            if (string != null)
                setDefaultButton(string);

        } finally {
            typedArray.recycle();
        }
    }

    // checks the default button based on the passed string
    private void setDefaultButton(String string) {
        final int START_OF_INDEX = 6;
        final int START_OF_TEXT = 5;

        // the text of the button to check
        String buttonToCheck;

        if (string.startsWith(XML_DEFAULT_BUTTON_PREFIX_INDEX)) {
            String indexString = string.substring(START_OF_INDEX, string.length());
            int index = Integer.parseInt(indexString);
            if (index < 0 || index >= mRadioButtons.size())
                throw new IllegalArgumentException("index must be between 0 to getRadioButtonCount() - 1 [" +
                        (getRadioButtonCount() - 1) + "]");
            buttonToCheck = mRadioButtons.get(index).getText().toString();

        } else if (string.startsWith(XML_DEFAULT_BUTTON_PREFIX_TEXT)) {
            buttonToCheck = string.substring(START_OF_TEXT, string.length());

        } else { // when there is no prefix assumes the string is the text of the button
            buttonToCheck = string;
        }

        check(buttonToCheck);
    }

    /**
     * Returns the table layout to set to this layout.
     *
     * @return the table layout
     */
    protected TableLayout getTableLayout() {
        return (TableLayout) LayoutInflater.from(getContext())
                .inflate(R.layout.table_layout, this, false);
    }

    /**
     * Returns the table row to set in this layout.
     *
     * @return the table row
     */
    protected TableRow getTableRow() {
        return (TableRow) LayoutInflater.from(getContext())
                .inflate(R.layout.table_row, mTableLayout, false);
    }

    /**
     * Returns the radio button to set in this layout.
     *
     * @return the radio button
     */
    protected RadioButton getRadioButton() {
        return (RadioButton) LayoutInflater.from(getContext())
                .inflate(R.layout.radio_button, null);
    }

    /**
     * Register a callback to be invoked when a radio button is checked.
     *
     * @param onCheckedChangeListener the listener to attach
     */
    public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
        this.mOnCheckedChangeListener = onCheckedChangeListener;
    }

    /**
     * Sets the maximum radio buttons in a row, 0 for all in one line
     * and arranges the layout accordingly.
     *
     * @param maxInRow the maximum radio buttons in a row
     * @throws IllegalArgumentException if maxInRow is negative
     */
    public void setMaxInRow(int maxInRow) {
        if (maxInRow < 0)
            throw new IllegalArgumentException("maxInRow must not be negative");
        this.mMaxInRow = maxInRow;
        arrangeButtons();
    }

    /**
     * Adds a view to the layout
     * <p>
     * Consider using addButtons() instead
     *
     * @param child the view to add
     */
    @Override
    public void addView(View child) {
        addView(child, -1, child.getLayoutParams());
    }

    /**
     * Adds a view to the layout in the specified index
     * <p>
     * Consider using addButtons() instead
     *
     * @param child the view to add
     * @param index the index in which to insert the view
     */
    @Override
    public void addView(View child, int index) {
        addView(child, index, child.getLayoutParams());
    }

    /**
     * Adds a view to the layout with the specified width and height.
     * Note that for radio buttons the width and the height are ignored.
     * <p>
     * Consider using addButtons() instead
     *
     * @param child  the view to add
     * @param width  the width of the view
     * @param height the height of the view
     */
    @Override
    public void addView(View child, int width, int height) {
        addView(child, -1, new LinearLayout.LayoutParams(width, height));
    }

    /**
     * Adds a view to the layout with the specified layout params.
     * Note that for radio buttons the params are ignored.
     * <p>
     * Consider using addButtons() instead
     *
     * @param child  the view to add
     * @param params the layout params of the view
     */
    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        addView(child, -1, params);
    }

    /**
     * Adds a view to the layout in the specified index
     * with the specified layout params.
     * Note that for radio buttons the params are ignored.
     * <p>
     * * Consider using addButtons() instead
     *
     * @param child  the view to add
     * @param index  the index in which to insert the view
     * @param params the layout params of the view
     */
    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (params == null) {
            params = new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        }

        if (child instanceof RadioButton)
            addButtons(index, ((RadioButton) child).getText());
        else
            super.addView(child, index, params);
    }

    /**
     * Adds radio buttons to the layout based on the texts in the radioButtons array.
     * Adds them in the last index.
     * If radioButtons is null does nothing.
     *
     * @param radioButtons the texts of the buttons to add
     */
    public void addButtons(CharSequence... radioButtons) {
        addButtons(-1, radioButtons);
    }

    /**
     * Adds radio buttons to the layout based on the texts in the radioButtons array.
     * Adds them in the specified index, -1 for the last index.
     * If radioButtons is null does nothing.
     *
     * @param index        the index in which to insert the radio buttons
     * @param radioButtons the texts of the buttons to add
     * @throws IllegalArgumentException if index is less than -1 or greater than the number of radio buttons
     */
    public void addButtons(int index, CharSequence... radioButtons) {
        if (index < -1 || index > mRadioButtons.size())
            throw new IllegalArgumentException("index must be between -1 to getRadioButtonCount() [" +
                    getRadioButtonCount() + "]");

        if (radioButtons == null)
            return;

        int realIndex = (index != -1) ? index : mRadioButtons.size();

        // adds the buttons to the list
        for (CharSequence text : radioButtons)
            mRadioButtons.add(realIndex++, createRadioButton(text));

        arrangeButtons();
    }

    // creates a radio button with the specified text
    private RadioButton createRadioButton(CharSequence text) {
        RadioButton radioButton = getRadioButton();
        radioButton.setText(text);
        radioButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                checkButton((RadioButton) v);

                if (mOnCheckedChangeListener != null)
                    mOnCheckedChangeListener.onCheckedChanged(MultiLineRadioGroup.this, checkedButton);
            }
        });
        return radioButton;
    }

    /**
     * Removes a view from the layout.
     * <p>
     * Consider using removeButton().
     *
     * @param view the view to remove
     */
    @Override
    public void removeView(View view) {
        super.removeView(view);
    }

    /**
     * Removes a view from the layout in the specified index.
     * <p>
     * Consider using removeButton().
     *
     * @param index the index from which to remove the view
     */
    @Override
    public void removeViewAt(int index) {
        super.removeViewAt(index);
    }

    /**
     * Removes the specified range of views from the layout.
     * <p>
     * Consider using removeButtons().
     *
     * @param start the start index to remove
     * @param count the number of views to remove
     */
    @Override
    public void removeViews(int start, int count) {
        super.removeViews(start, count);
    }

    /**
     * Removes all the views from the layout.
     * <p>
     * Consider using removeAllButtons().
     */
    @Override
    public void removeAllViews() {
        super.removeAllViews();
    }

    /**
     * Removes a radio button from the layout.
     * If the radio button is null does nothing.
     *
     * @param radioButton the radio button to remove
     */
    public void removeButton(RadioButton radioButton) {
        if (radioButton == null)
            return;

        removeButton(radioButton.getText());
    }

    /**
     * Removes a radio button from the layout based on its text.
     * Removes the first occurrence.
     * If the text is null does nothing.
     *
     * @param text the text of the radio button to remove
     */
    public void removeButton(CharSequence text) {
        if (text == null)
            return;

        int index = -1;

        for (int i = 0, len = mRadioButtons.size(); i < len; i++) {
            // checks if the texts are equal
            if (mRadioButtons.get(i).getText().equals(text)) {
                index = i;
                break;
            }
        }

        // removes just if the index was found
        if (index != -1)
            removeButton(index);
    }

    /**
     * Removes the radio button in the specified index from the layout.
     *
     * @param index the index from which to remove the radio button
     * @throws IllegalArgumentException if index is less than 0
     *                                  or greater than the number of radio buttons - 1
     */
    public void removeButton(int index) {
        removeButtons(index, 1);
    }

    /**
     * Removes all the radio buttons in the specified range from the layout.
     * Count can be any non-negative number.
     *
     * @param start the start index to remove
     * @param count the number of radio buttons to remove
     * @throws IllegalArgumentException if index is less than 0
     *                                  or greater than the number of radio buttons - 1
     *                                  or count is negative
     */
    public void removeButtons(int start, int count) {
        if (start < 0 || start >= mRadioButtons.size())
            throw new IllegalArgumentException("remove index must be between 0 to getRadioButtonCount() - 1 [" +
                    (getRadioButtonCount() - 1) + "]");

        if (count < 0)
            throw new IllegalArgumentException("count must not be negative");

        if (count == 0)
            return;

        int endIndex = start + count - 1;
        // if endIndex is not in the range of the radio buttons sets it to the last index
        if (endIndex >= mRadioButtons.size())
            endIndex = mRadioButtons.size() - 1;

        // iterates over the buttons to remove
        for (int i = endIndex; i >= start; i--) {
            RadioButton radiobutton = mRadioButtons.get(i);
            // if the button to remove is the checked button set checkedButton to null
            if (radiobutton == checkedButton)
                checkedButton = null;
            // removes the button from the list
            mRadioButtons.remove(i);
        }

        arrangeButtons();
    }

    /**
     * Removes all the radio buttons from the layout.
     */
    public void removeAllButtons() {
        removeButtons(0, mRadioButtons.size());
    }

    // arrange the button in the layout
    private void arrangeButtons() {
        // iterates over each button and puts it in the right place
        for (int i = 0, len = mRadioButtons.size(); i < len; i++) {
            RadioButton radioButtonToPlace = mRadioButtons.get(i);
            int rowToInsert = (mMaxInRow != 0) ? i / mMaxInRow : 0;
            int columnToInsert = (mMaxInRow != 0) ? i % mMaxInRow : i;
            // gets the row to insert. if there is no row create one
            TableRow tableRowToInsert = (mTableLayout.getChildCount() <= rowToInsert)
                    ? addTableRow() : (TableRow) mTableLayout.getChildAt(rowToInsert);
            int tableRowChildCount = tableRowToInsert.getChildCount();

            // if there is already a button in the position
            if (tableRowChildCount > columnToInsert) {
                RadioButton currentButton = (RadioButton) tableRowToInsert.getChildAt(columnToInsert);

                // insert the button just if the current button is different
                if (currentButton != radioButtonToPlace) {
                    // removes the current button
                    removeButtonFromParent(currentButton, tableRowToInsert);
                    // removes the button to place from its current position
                    removeButtonFromParent(radioButtonToPlace, (ViewGroup) radioButtonToPlace.getParent());
                    // adds the button to the right place
                    tableRowToInsert.addView(radioButtonToPlace, columnToInsert);
                }

                // if there isn't already a button in the position
            } else {
                // removes the button to place from its current position
                removeButtonFromParent(radioButtonToPlace, (ViewGroup) radioButtonToPlace.getParent());
                // adds the button to the right place
                tableRowToInsert.addView(radioButtonToPlace, columnToInsert);
            }
        }

        removeRedundancies();
    }

    // removes the redundant rows and radio buttons
    private void removeRedundancies() {
        // the number of rows to fit the buttons
        int rows;
        if (mRadioButtons.size() == 0)
            rows = 0;
        else if (mMaxInRow == 0)
            rows = 1;
        else
            rows = (mRadioButtons.size() - 1) / mMaxInRow + 1;

        int tableChildCount = mTableLayout.getChildCount();
        // if there are redundant rows remove them
        if (tableChildCount > rows)
            mTableLayout.removeViews(rows, tableChildCount - rows);

        tableChildCount = mTableLayout.getChildCount();
        int maxInRow = (mMaxInRow != 0) ? mMaxInRow : mRadioButtons.size();

        // iterates over the rows
        for (int i = 0; i < tableChildCount; i++) {
            TableRow tableRow = (TableRow) mTableLayout.getChildAt(i);
            int tableRowChildCount = tableRow.getChildCount();

            int startIndexToRemove;
            int count;

            // if it is the last row removes all redundancies after the last button in the list
            if (i == tableChildCount - 1) {
                startIndexToRemove = (mRadioButtons.size() - 1) % maxInRow + 1;
                count = tableRowChildCount - startIndexToRemove;

                // if it is not the last row removes all the buttons after maxInRow position
            } else {
                startIndexToRemove = maxInRow;
                count = tableRowChildCount - maxInRow;
            }

            if (count > 0)
                tableRow.removeViews(startIndexToRemove, count);
        }
    }

    // adds and returns a table row
    private TableRow addTableRow() {
        TableRow tableRow = getTableRow();
        mTableLayout.addView(tableRow);
        return tableRow;
    }

    // removes a radio button from a parent
    private void removeButtonFromParent(RadioButton radioButton, ViewGroup parent) {
        if (radioButton == null || parent == null)
            return;

        parent.removeView(radioButton);
    }

    /**
     * Returns the number of radio buttons.
     *
     * @return the number of radio buttons
     */
    public int getRadioButtonCount() {
        return mRadioButtons.size();
    }

    /**
     * Returns the radio button in the specified index.
     * If the index is out of range returns null.
     *
     * @param index the index of the radio button
     * @return the radio button
     */
    public RadioButton getRadioButtonAt(int index) {
        if (index < 0 || index >= mRadioButtons.size())
            return null;

        return mRadioButtons.get(index);
    }

    /**
     * Checks the radio button with the specified id.
     * If the specified id is not found does nothing.
     *
     * @param id the radio button's id
     */
    @Override
    public void check(int id) {
        if (id <= 0)
            return;

        for (RadioButton radioButton : mRadioButtons) {
            if (radioButton.getId() == id) {
                checkButton(radioButton);
                return;
            }
        }
    }

    /**
     * Checks the radio button with the specified text.
     * If there is more than one radio button associated with this text
     * checks the first radio button.
     * If the specified text is not found does nothing.
     *
     * @param text the radio button's text
     */
    public void check(CharSequence text) {
        if (text == null)
            return;

        for (RadioButton radioButton : mRadioButtons) {
            if (radioButton.getText().equals(text)) {
                checkButton(radioButton);
                return;
            }
        }
    }

    /**
     * Checks the radio button at the specified index.
     * If the specified index is invalid does nothing.
     *
     * @param index the radio button's index
     */
    public void checkAt(int index) {
        if (index < 0 || index >= mRadioButtons.size())
            return;

        checkButton(mRadioButtons.get(index));
    }

    // checks and switches the button with the checkedButton
    private void checkButton(RadioButton button) {
        if (button == null)
            return;

        // if the button to check is different from the current checked button
        if (button != checkedButton) {

            // if exists sets checkedButton to null
            if (checkedButton != null)
                checkedButton.setChecked(false);

            button.setChecked(true);
            checkedButton = button;
        }
    }

    /**
     * Clears the checked radio button
     */
    @Override
    public void clearCheck() {
        checkedButton.setChecked(false);
        checkedButton = null;
    }

    /**
     * Returns the checked radio button's id.
     * If no radio buttons are checked returns -1.
     *
     * @return the checked radio button's id
     */
    @Override
    public int getCheckedRadioButtonId() {
        if (checkedButton == null)
            return -1;

        return checkedButton.getId();
    }

    /**
     * Returns the checked radio button's index.
     * If no radio buttons are checked returns -1.
     *
     * @return the checked radio button's index
     */
    public int getCheckedRadioButtonIndex() {
        if (checkedButton == null)
            return -1;

        return mRadioButtons.indexOf(checkedButton);
    }

    /**
     * Returns the checked radio button's text.
     * If no radio buttons are checked returns null.
     *
     * @return the checked radio buttons's text
     */
    public CharSequence getCheckedRadioButtonText() {
        if (checkedButton == null)
            return null;

        return checkedButton.getText();
    }

    /**
     * Interface definition for a callback to be invoked when a radio button is checked.
     */
    public interface OnCheckedChangeListener {
        /**
         * Called when a radio button is checked.
         *
         * @param group  the MultiLineRadioGroup that stores the radio button
         * @param button the radio button that was checked
         */
        void onCheckedChanged(MultiLineRadioGroup group, RadioButton button);
    }
}

values/attrs.xml file:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="multi_line_radio_group">
        <attr name="max_in_row" format="integer" />
        <attr name="radio_buttons" format="reference" />
        <attr name="default_button" format="string" />
    </declare-styleable>
</resources>

R.layout.table_layout:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/table_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:stretchColumns="*" />

R.layout.table_row:

<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/table_row"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

R.layout.radio_button:(you can change the text size here)

<?xml version="1.0" encoding="utf-8"?>
<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/radio_button"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:textSize="@dimen/radio_button_text_size" />

Example using this layout from xml:

<?xml version="1.0" encoding="utf-8"?>
<[package].MultiLineRadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:multi_line_radio_group="http://schemas.android.com/apk/res-auto"
    android:id="@+id/multi_line_radio_group"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    multi_line_radio_group:default_button="@string/defaultText"
    multi_line_radio_group:max_in_row="@integer/radio_button_max_in_row"
    multi_line_radio_group:radio_buttons="@array/radio_buttons" />
Longerich answered 12/12, 2016 at 13:57 Comment(0)
N
1

I created my own RadioGridLayout which include RadioGroup code and extends GridLayout. You can copy this code. For me working well. After you can use this layout in your xml. And customize like grid layout.

For R.styleable.RadioGridLayout_checked I used code like this:

<resources>
    <declare-styleable name="RadioGridLayout">
        <attr name="checked" format="integer" />
    </declare-styleable>
</resources>
public class RadioGridLayout extends GridLayout {

    private int mCheckedId = -1;
    private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
    private boolean mProtectFromCheckedChange = false;
    private OnCheckedChangeListener mOnCheckedChangeListener;
    private PassThroughHierarchyChangeListener mPassThroughListener;

    private void setCheckedId(@IdRes int id) {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
        }
        AutofillManager afm = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            afm = getContext().getSystemService(AutofillManager.class);
        }
        if (afm != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                afm.notifyValueChanged(this);
            }
        }
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
        mOnCheckedChangeListener = listener;
    }

    public interface OnCheckedChangeListener {
        void onCheckedChanged(RadioGridLayout group, @IdRes int checkedId);
    }

    private int mInitialCheckedId = View.NO_ID;

    public RadioGridLayout(Context context) {
        super(context);
        setOrientation(VERTICAL);
        init();
    }

    public RadioGridLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
                setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
            }
        }

        TypedArray attributes = context.obtainStyledAttributes(
                attrs,
                R.styleable.RadioGridLayout,
                R.attr.radioButtonStyle, 0);

        int value = attributes.getResourceId(R.styleable.RadioGridLayout_checked, View.NO_ID);
        if (value != View.NO_ID) {
            mCheckedId = value;
            mInitialCheckedId = value;
        }

        attributes.recycle();
        init();
    }

    private void init() {
        mChildOnCheckedChangeListener = new CheckedStateTracker();
        mPassThroughListener = new PassThroughHierarchyChangeListener();
        super.setOnHierarchyChangeListener(mPassThroughListener);
    }

    @Override
    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
        mPassThroughListener.mOnHierarchyChangeListener = listener;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (mCheckedId != -1) {
            mProtectFromCheckedChange = true;
            setCheckedStateForView(mCheckedId, true);
            mProtectFromCheckedChange = false;
            setCheckedId(mCheckedId);
        }
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (child instanceof RadioButton) {
            final RadioButton button = (RadioButton) child;
            if (button.isChecked()) {
                mProtectFromCheckedChange = true;
                if (mCheckedId != -1) {
                    setCheckedStateForView(mCheckedId, false);
                }
                mProtectFromCheckedChange = false;
                setCheckedId(button.getId());
            }
        }

        super.addView(child, index, params);
    }

    public void check(@IdRes int id) {
        if (id != -1 && (id == mCheckedId)) {
            return;
        }

        if (mCheckedId != -1) {
            setCheckedStateForView(mCheckedId, false);
        }

        if (id != -1) {
            setCheckedStateForView(id, true);
        }

        setCheckedId(id);
    }

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

    @IdRes
    public int getCheckedRadioButtonId() {
        return mCheckedId;
    }

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

    @Override
    public GridLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new GridLayout.LayoutParams(getContext(), attrs);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof RadioGroup.LayoutParams;
    }

    @Override
    protected GridLayout.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams();
    }

    @Override
    public CharSequence getAccessibilityClassName() {
        return RadioGroup.class.getName();
    }

    public static class LayoutParams extends GridLayout.LayoutParams {

        public LayoutParams(Spec rowSpec, Spec columnSpec) {
            super(rowSpec, columnSpec);
        }

        public LayoutParams() {
            super();
        }

        public LayoutParams(ViewGroup.LayoutParams params) {
            super(params);
        }

        public LayoutParams(MarginLayoutParams params) {
            super(params);
        }

        public LayoutParams(GridLayout.LayoutParams source) {
            super(source);
        }

        public LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

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

    private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (mProtectFromCheckedChange) {
                return;
            }

            mProtectFromCheckedChange = true;
            if (mCheckedId != -1) {
                setCheckedStateForView(mCheckedId, false);
            }
            mProtectFromCheckedChange = false;

            int id = buttonView.getId();
            setCheckedId(id);
        }
    }

    private class PassThroughHierarchyChangeListener implements
            ViewGroup.OnHierarchyChangeListener {
        private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;

        @Override
        public void onChildViewAdded(View parent, View child) {
            if (parent == RadioGridLayout.this && child instanceof RadioButton) {
                int id = child.getId();
                if (id == View.NO_ID) {
                    id = View.generateViewId();
                    child.setId(id);
                }
                ((RadioButton) child).setOnCheckedChangeListener(
                        mChildOnCheckedChangeListener);
            }

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

        @Override
        public void onChildViewRemoved(View parent, View child) {
            if (parent == RadioGridLayout.this && child instanceof RadioButton) {
                ((RadioButton) child).setOnCheckedChangeListener(null);
            }

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

    @Override
    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
        super.onProvideAutofillStructure(structure, flags);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            structure.setDataIsSensitive(mCheckedId != mInitialCheckedId);
        }
    }

    @Override
    public void autofill(AutofillValue value) {
        if (!isEnabled()) return;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (!value.isList()) {
                Timber.w(value + " could not be autofilled into " + this);
                return;
            }
        }

        int index = 0;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            index = value.getListValue();
        }
        final View child = getChildAt(index);
        if (child == null) {
            Timber.w("RadioGroup.autoFill(): no child with index %s", index);
            return;
        }

        check(child.getId());
    }

    @Override
    public int getAutofillType() {
        return isEnabled() ? AUTOFILL_TYPE_LIST : AUTOFILL_TYPE_NONE;
    }

    @Override
    public AutofillValue getAutofillValue() {
        if (!isEnabled()) return null;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getId() == mCheckedId) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    return AutofillValue.forList(i);
                }
            }
        }
        return null;
    }
}
Novokuznetsk answered 9/7, 2019 at 9:10 Comment(0)
E
1

this solution for handle click inside click body

xml :

             <LinearLayout
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal"
                        android:weightSum="2">

                        <RadioGroup
                            android:id="@+id/radioGroup_action_1"
                            android:layout_width="0dp"
                            android:layout_height="wrap_content"
                            android:layout_marginStart="16dp"
                            android:layout_marginTop="16dp"
                            android:layout_marginEnd="16dp"
                            android:layout_marginBottom="16dp"
                            android:layout_weight="1"
                            android:orientation="vertical">

                            <RadioButton
                                android:id="@+id/radio_a"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:text="@string/a"
                                android:textColor="@color/textColor"
                                 />

                            <RadioButton
                                android:id="@+id/radio_b"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:text="@string/b"
                                android:textColor="@color/textColor" />


                            <RadioButton
                                android:id="@+id/radio_c"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:text="@string/c"
                                android:textColor="@color/textColor" />
                        </RadioGroup>

                        <RadioGroup
                            android:id="@+id/radioGroup_action_2"
                            android:layout_width="0dp"
                            android:layout_height="wrap_content"
                            android:layout_marginStart="16dp"
                            android:layout_marginTop="16dp"
                            android:layout_marginEnd="16dp"
                            android:layout_marginBottom="16dp"
                            android:layout_weight="1"
                            android:orientation="vertical">

                            <RadioButton
                                android:id="@+id/radio_d"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:checked="true"
                                android:text="@string/d"
                                android:textColor="@color/textColor" />

                            <RadioButton
                                android:id="@+id/radio_e"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:text="@string/e"
                                android:textColor="@color/textColor" />

                            <RadioButton
                                android:id="@+id/f"
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:text="@string/f"
                                android:textColor="@color/textColor" />

                        </RadioGroup>
                    </LinearLayout>

code :

 private var isChecking = true

 mBinding.radioGroupAction1.setOnCheckedChangeListener { radioGroup, i ->
        if (i != -1 && isChecking) {
            isChecking = false
            mBinding.radioGroupAction2.clearCheck()
        }
        isChecking = true
        when (i) {
            R.id.a -> {
               Log.e("radioGroupAction:", "a")
            }
            R.id.b -> {
               Log.e("radioGroupAction:", "b")
            }
            R.id.c -> {
               Log.e("radioGroupAction:", "c")
            }
        }
    }
    mBinding.radioGroupAction2.setOnCheckedChangeListener { radioGroup, i ->
        if (i != -1 && isChecking) {
            isChecking = false
            mBinding.radioGroupAction1.clearCheck()
        }
        isChecking = true
        when (i) {
            R.id.d -> {
               Log.e("radioGroupAction:", "d")
            }
            R.id.e -> {
               Log.e("radioGroupAction:", "e")
            }
            R.id.f -> {
               Log.e("radioGroupAction:", "f")
            }
        }
    }

Good Luck

Embassy answered 14/9, 2020 at 11:35 Comment(0)
O
0

Kotlin Code:

package com.test.yourproject.utility

import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.CompoundButton
import androidx.appcompat.widget.AppCompatRadioButton
import java.util.concurrent.atomic.AtomicInteger
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull
import androidx.gridlayout.widget.GridLayout


class RadioGridGroup : GridLayout {
    var checkedCheckableImageButtonId = -1
        private set
    private var mChildOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
    private var mProtectFromCheckedChange = false
    private var mOnCheckedChangeListener: OnCheckedChangeListener? = null
    private var mPassThroughListener: PassThroughHierarchyChangeListener? = null

    constructor(context: Context?) : super(context) {
        init()
    }

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }

    private fun init() {
        mChildOnCheckedChangeListener = CheckedStateTracker()
        mPassThroughListener = PassThroughHierarchyChangeListener()
        super.setOnHierarchyChangeListener(mPassThroughListener)
    }

    override fun setOnHierarchyChangeListener(listener: OnHierarchyChangeListener?) {
        mPassThroughListener!!.mOnHierarchyChangeListener = listener
    }

    protected override fun onFinishInflate() {
        super.onFinishInflate()
        if (checkedCheckableImageButtonId != -1) {
            mProtectFromCheckedChange = true
            setCheckedStateForView(checkedCheckableImageButtonId, true)
            mProtectFromCheckedChange = false
            setCheckedId(checkedCheckableImageButtonId)
        }
    }

    override fun addView(@NonNull child: View, index: Int, params: ViewGroup.LayoutParams?) {
        if (child is AppCompatRadioButton) {
            val button = child as AppCompatRadioButton
            if (button.isChecked) {
                mProtectFromCheckedChange = true
                if (checkedCheckableImageButtonId != -1) {
                    setCheckedStateForView(checkedCheckableImageButtonId, false)
                }
                mProtectFromCheckedChange = false
                setCheckedId(button.id)
            }
        }
        super.addView(child, index, params)
    }

    fun check(id: Int) {
        if (id != -1 && id == checkedCheckableImageButtonId) {
            return
        }
        if (checkedCheckableImageButtonId != -1) {
            setCheckedStateForView(checkedCheckableImageButtonId, false)
        }
        if (id != -1) {
            setCheckedStateForView(id, true)
        }
        setCheckedId(id)
    }

    private fun setCheckedId(id: Int) {
        checkedCheckableImageButtonId = id
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener!!.onCheckedChanged(this, checkedCheckableImageButtonId)
        }
    }

    private fun setCheckedStateForView(viewId: Int, checked: Boolean) {
        val checkedView: View = findViewById(viewId)
        if (checkedView != null && checkedView is AppCompatRadioButton) {
            (checkedView as AppCompatRadioButton).isChecked = checked
        }
    }

    fun clearCheck() {
        check(-1)
    }

    fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
        mOnCheckedChangeListener = listener
    }

    override fun onInitializeAccessibilityEvent(@NonNull event: AccessibilityEvent) {
        super.onInitializeAccessibilityEvent(event)
        event.className = RadioGridGroup::class.java.name
    }

    override fun onInitializeAccessibilityNodeInfo(@NonNull info: AccessibilityNodeInfo) {
        super.onInitializeAccessibilityNodeInfo(info)
        info.className = RadioGridGroup::class.java.name
    }

    interface OnCheckedChangeListener {
        fun onCheckedChanged(group: RadioGridGroup?, checkedId: Int)
    }

    private inner class CheckedStateTracker : CompoundButton.OnCheckedChangeListener {
        override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
            if (mProtectFromCheckedChange) {
                return
            }
            mProtectFromCheckedChange = true
            if (checkedCheckableImageButtonId != -1) {
                setCheckedStateForView(checkedCheckableImageButtonId, false)
            }
            mProtectFromCheckedChange = false
            val id = buttonView.id
            setCheckedId(id)
        }
    }

    private inner class PassThroughHierarchyChangeListener :
        OnHierarchyChangeListener {
        var mOnHierarchyChangeListener: OnHierarchyChangeListener? = null
        override fun onChildViewAdded(parent: View, child: View) {
            if (parent === this@RadioGridGroup && child is AppCompatRadioButton) {
                var id: Int = child.getId()
                // generates an id if it's missing
                if (id == View.NO_ID) {
                    id = generateViewId()
                    child.setId(id)
                }
                (child as AppCompatRadioButton).setOnCheckedChangeListener(
                    mChildOnCheckedChangeListener
                )
            }
            mOnHierarchyChangeListener?.onChildViewAdded(parent, child)
        }

        override fun onChildViewRemoved(parent: View, child: View?) {
            if (parent === this@RadioGridGroup && child is AppCompatRadioButton) {
                (child as AppCompatRadioButton).setOnCheckedChangeListener(null)
            }
            mOnHierarchyChangeListener?.onChildViewRemoved(parent, child)
        }
    }

    companion object {
        private val sNextGeneratedId: AtomicInteger = AtomicInteger(1)
        fun generateViewId(): Int {
            while (true) {
                val result: Int = sNextGeneratedId.get()

                // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
                var newValue = result + 1
                if (newValue > 0x00FFFFFF) newValue = 1 // Roll over to 1, not 0.
                if (sNextGeneratedId.compareAndSet(result, newValue)) {
                    return result
                }
            }
        }
    }
}

XML:

<com.test.yourproject.utility.RadioGridGroup
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    xmlns:grid="http://schemas.android.com/apk/res-auto"
                    grid:columnCount="3"
                    android:checkedButton="@+id/radio_button_date_range"
                    grid:useDefaultMargins="true">

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        android:checked="false"
                        android:text="Text1"
                        grid:layout_columnWeight="1"/>

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        android:text="Text2"
                        grid:layout_columnWeight="1"/>

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:text="Text3"
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        grid:layout_columnWeight="1"/>

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:text="Text4"
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        grid:layout_columnWeight="1"/>

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:text="Text5"
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        grid:layout_columnWeight="1"/>

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:text="Text6"
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        grid:layout_columnWeight="1"/>

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:text="Text7"
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        grid:layout_columnWeight="1"/>

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:text="Text8"
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        grid:layout_columnWeight="1"/>

                    <androidx.appcompat.widget.AppCompatRadioButton
                        android:text="Text9"
                        android:layout_height="wrap_content"
                        android:layout_width="wrap_content"
                        grid:layout_columnWeight="1"/>

                </com.test.yourproject.utility.RadioGridGroup>
Okinawa answered 3/5, 2012 at 5:23 Comment(0)
G
0

A couple of the other answers work fine, but are more complicated than required for simpler cases. If you just want multiple RadioGroups to act as one and can handle all of the decision making at the time of the click, then you can do this:

In your layout XML, add the same click handler to all of the RadioButtons that you want combined:

android:onClick="handleCombinedClick"

Then, make your click handler look something like this:

public void handleCombinedClick(View view) {
    // Clear any checks from both groups:
    rg1.clearCheck();
    rg2.clearCheck();

    // Manually set the check in the newly clicked radio button:
    ((RadioButton) view).setChecked(true);

    // Perform any action desired for the new selection:
    switch (view.getId()) {
        case R.id.radio_button_1:
            // do something
            break;

        case R.id.radio_button_2:
            // do something
            break;

        ...
    }
}

This has the added benefit of having all of your choices handled in the same spot. And if you want to extend this to 3 or more RadioGroups, then you just need to add an additional rgX.clearCheck(); line for each added group.

Gonophore answered 25/3, 2016 at 21:41 Comment(0)
S
0

I'm sure your question has been answered by now, but here is another take on it. With this code you can wrap your radio buttons into any layout you want (you don't need a radio group at all in fact). Also I would suggest using linear layouts to make the columns/rows you need.

My code is based on @infografnet and @lostdev (also thanks @Neromancer for the Compound Button suggestion!)

public class AdvRadioGroup {
    public interface OnButtonCheckedListener {
        void onButtonChecked(CompoundButton button);
    }

    private final List<CompoundButton> buttons;
    private final View.OnClickListener onClick = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            setChecked((CompoundButton) v);
        }
    };

    private OnButtonCheckedListener listener;
    private CompoundButton lastChecked;


    public AdvRadioGroup(View view) {
        buttons = new ArrayList<>();
        parseView(view);
    }

    private void parseView(final View view) {
        if(view instanceof CompoundButton) {
            buttons.add((CompoundButton) view);
            view.setOnClickListener(onClick);
        } else if(view instanceof ViewGroup) {
            final ViewGroup group = (ViewGroup) view;
            for (int i = 0; i < group.getChildCount();i++) {
                parseView(group.getChildAt(i));
            }
        }
    }

    public List<CompoundButton> getButtons() { return buttons; }

    public CompoundButton getLastChecked() { return lastChecked; }

    public void setChecked(int index) { setChecked(buttons.get(index)); }

    public void setChecked(CompoundButton button) {
        if(button == lastChecked) return;

        for (CompoundButton btn : buttons) {
            btn.setChecked(false);
        }

        button.setChecked(true);

        lastChecked = button;

        if(listener != null) {
            listener.onButtonChecked(button);
        }
    }

    public void setOnButtonCheckedListener(OnButtonCheckedListener listener) { this.listener = listener; }
}

Usage (with included listener):

AdvRadioGroup group = new AdvRadioGroup(findViewById(R.id.YOUR_VIEW));
group.setOnButtonCheckedListener(new AdvRadioGroup.OnButtonCheckedListener() {
    @Override
    public void onButtonChecked(CompoundButton button) {
        // do fun stuff here!
    }
});

Bonus: You can get the last checked button, the list of entire buttons, and you can check any button by index with this!

Stamen answered 3/8, 2017 at 7:25 Comment(0)
R
0

This is what i did on my XML layout and it works fine.

  <RadioGroup
                android:id="@+id/radioGroup"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="2dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                <RadioButton
                    android:id="@+id/radioOwner"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:buttonTint="@color/login_button_color"
                    android:fontFamily="@font/rnhousesans_regular"
                    android:text="Owner"
                    android:textColor="@color/colorPrimary" />

                <RadioButton
                    android:id="@+id/radioLivingParents"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:buttonTint="@color/login_button_color"
                    android:fontFamily="@font/rnhousesans_regular"
                    android:text="Living with parents"
                    android:textColor="@color/colorPrimary" />

                </LinearLayout>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                <RadioButton
                    android:id="@+id/radioTenant"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:buttonTint="@color/login_button_color"
                    android:fontFamily="@font/rnhousesans_regular"
                    android:text="Tenant"
                    android:textColor="@color/colorPrimary" />

                <RadioButton
                    android:id="@+id/radioOther"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:buttonTint="@color/login_button_color"
                    android:fontFamily="@font/rnhousesans_regular"
                    android:text="Other"
                    android:textColor="@color/colorPrimary" />

                </LinearLayout>
            </RadioGroup>
Riding answered 19/11, 2019 at 7:27 Comment(0)
S
0

You can use a nested GridLayout inside your RadioGroup, although you will loose the main property of the RadioGroup to manage RadioButtons: e.g. single item selection. RadioButtons should be direct children.

<RadioGroup
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    >

    <GridLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:columnCount="2"
        >

    <androidx.appcompat.widget.AppCompatRadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text 1"
        />

    <androidx.appcompat.widget.AppCompatRadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text 2"
        />

    <androidx.appcompat.widget.AppCompatRadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text 3"
        />

    <androidx.appcompat.widget.AppCompatRadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text 4"
        />

    <androidx.appcompat.widget.AppCompatRadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text 5"
        />

    </GridLayout>
</RadioGroup>
Schluter answered 23/4, 2020 at 20:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.