Skip to content

Instantly share code, notes, and snippets.

@saiaspire
Last active March 24, 2024 10:33
Show Gist options
  • Save saiaspire/a73135cfee1110a64cb0ab3451b6ca33 to your computer and use it in GitHub Desktop.
Save saiaspire/a73135cfee1110a64cb0ab3451b6ca33 to your computer and use it in GitHub Desktop.
A Radio Group that has GridLayout as its parent. This allows for more complex layouts of RadioButton (use AppCompatRadioButton). For example, when you need multiple rows of Radio Buttons.
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.AppCompatRadioButton;
import android.support.v7.widget.GridLayout;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.CompoundButton;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>This class is used to create a multiple-exclusion scope for a set of radio
* buttons. Checking one radio button that belongs to a radio group unchecks
* any previously checked radio button within the same group.</p>
* <p/>
* <p>Intially, all of the radio buttons are unchecked. While it is not possible
* to uncheck a particular radio button, the radio group can be cleared to
* remove the checked state.</p>
* <p/>
* <p>The selection is identified by the unique id of the radio button as defined
* in the XML layout file.</p>
* <p/>
* <p>See
* {@link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
* for layout attributes.</p>
*
* @see AppCompatRadioButton
*/
public class RadioGridGroup extends GridLayout {
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
private int mCheckedId = -1;
private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
private boolean mProtectFromCheckedChange = false;
private OnCheckedChangeListener mOnCheckedChangeListener;
private PassThroughHierarchyChangeListener mPassThroughListener;
public RadioGridGroup(Context context) {
super(context);
init();
}
public RadioGridGroup(Context context, AttributeSet attrs) {
super(context, attrs);
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(@NonNull View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof AppCompatRadioButton) {
final AppCompatRadioButton button = (AppCompatRadioButton) 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(int id) {
if (id != -1 && (id == mCheckedId)) {
return;
}
if (mCheckedId != -1) {
setCheckedStateForView(mCheckedId, false);
}
if (id != -1) {
setCheckedStateForView(id, true);
}
setCheckedId(id);
}
private void setCheckedId(int id) {
mCheckedId = id;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}
}
private void setCheckedStateForView(int viewId, boolean checked) {
View checkedView = findViewById(viewId);
if (checkedView != null && checkedView instanceof AppCompatRadioButton) {
((AppCompatRadioButton) checkedView).setChecked(checked);
}
}
public int getCheckedCheckableImageButtonId() {
return mCheckedId;
}
public void clearCheck() {
check(-1);
}
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
@Override
public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(RadioGridGroup.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(RadioGridGroup.class.getName());
}
public interface OnCheckedChangeListener {
void onCheckedChanged(RadioGridGroup group, int checkedId);
}
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;
public void onChildViewAdded(View parent, View child) {
if (parent == RadioGridGroup.this && child instanceof AppCompatRadioButton) {
int id = child.getId();
// generates an id if it's missing
if (id == View.NO_ID) {
id = generateViewId();
child.setId(id);
}
((AppCompatRadioButton) child).setOnCheckedChangeListener(
mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
}
}
public void onChildViewRemoved(View parent, View child) {
if (parent == RadioGridGroup.this && child instanceof AppCompatRadioButton) {
((AppCompatRadioButton) child).setOnCheckedChangeListener(null);
}
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
}
public static int generateViewId() {
for (; ; ) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
}
@mgpx
Copy link

mgpx commented Aug 24, 2020

How to implement this script into xml ?

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

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

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

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

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

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

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

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

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

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

    </com.mypackage.example.RadioGridGroup>

@dimaslanjaka
Copy link

thanks very much

@flomingdev
Copy link

error: attribute columnCount not found

@TSFAKR
Copy link

TSFAKR commented Jul 20, 2023

error: attribute columnCount not found.

Use
android:columnCount="3" instead of grid:columnCount="3"

in my case it is fixed by this way.

@Leslyerivera
Copy link

Leslyerivera commented Aug 7, 2023

Hi, thanks for the code! I am using it on my app and the buttons work but i cant align them inside a 2 column 3 rows grid, all of them stack on top of each other and i dont know how to fix it. I have a grid layout as a parent of the radio grid group. Before this I used a grid layout with a radio button group and the buttons were correctly placed but i couldnt select only one option. Now I have the opposite problem. Any thoughts on how to solve this?

<GridLayout
    android:id="@+id/topic_buttons"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:rowCount="3"
    android:columnCount="2"
    android:orientation="horizontal"
    android:layout_marginTop="20dp"
    app:layout_constraintTop_toBottomOf="@+id/Instruction_6"
    android:paddingHorizontal="35dp">

    <com.example.myapp.RadioGridGroup
        android:id="@+id/radioGridGroup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:columnCount="2"
        android:useDefaultMargins="true"
        >

        <androidx.appcompat.widget.AppCompatRadioButton
            android:fontFamily="@font/merriweather_sans_bold"
            android:id="@+id/topic_buttons_conflict"
            android:layout_width="0dp"
            android:layout_height="80dp"
            android:paddingVertical="20dp"
            android:paddingHorizontal="0dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="25dp"
            android:layout_columnWeight="1"
            />

        <!-- Other radio buttons go here -->

    </com.example.myapp.RadioGridGroup>
</GridLayout>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment