Last active
March 24, 2024 10:33
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
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>
thanks very much
error: attribute columnCount not found
error: attribute columnCount not found.
Use
android:columnCount="3"
instead of grid:columnCount="3"
in my case it is fixed by this way.
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
How to implement this script into xml ?