Skip to content

Instantly share code, notes, and snippets.

@webserveis
Last active January 10, 2022 23:07
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webserveis/53a31b1665cf0f7cfeda5264ac49fab6 to your computer and use it in GitHub Desktop.
Save webserveis/53a31b1665cf0f7cfeda5264ac49fab6 to your computer and use it in GitHub Desktop.
RadioGridGroupLayout
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/square_round_on" android:state_checked="true" />
<item android:drawable="@drawable/square_round_off" android:state_checked="false" />
</selector>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?android:colorControlActivated" android:state_checked="true" />
<item android:color="?android:colorControlNormal" android:state_checked="false" />
</selector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".FirstFragment">
<com.webserveis.app.testradiogridlayout.RadioGridGroupLayout
android:id="@+id/gridRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:columnCount="2">
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/option_1"
style="@style/GridRadioButton"
android:checked="true"
android:drawableTop="@drawable/small_yoga1"
android:text="Beginner essentials"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/option_2"
style="@style/GridRadioButton"
android:drawableTop="@drawable/small_yoga2"
android:text="Intermediate essentials"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
tools:checked="true" />
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/option_3"
style="@style/GridRadioButton"
android:drawableTop="@drawable/small_yoga3"
android:text="Advanced essentials"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
tools:checked="true" />
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/option_4"
style="@style/GridRadioButton"
android:enabled="false"
android:text="Sun salutations"
app:drawableTopCompat="@drawable/small_yoga4"
app:layout_columnWeight="1"
app:layout_rowWeight="1" />
</com.webserveis.app.testradiogridlayout.RadioGridGroupLayout>
</LinearLayout>
package com.webserveis.app.testradiogridlayout
import android.content.Context
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 androidx.appcompat.widget.AppCompatRadioButton
import androidx.gridlayout.widget.GridLayout
import java.util.concurrent.atomic.AtomicInteger
/*
https://gist.github.com/saiaspire/a73135cfee1110a64cb0ab3451b6ca33
*/
class RadioGridGroupLayout : 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
}
override fun onFinishInflate() {
super.onFinishInflate()
if (checkedCheckableImageButtonId != -1) {
mProtectFromCheckedChange = true
setCheckedStateForView(checkedCheckableImageButtonId, true)
mProtectFromCheckedChange = false
setCheckedId(checkedCheckableImageButtonId)
}
}
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams?) {
if (child is AppCompatRadioButton) {
if (child.isChecked) {
mProtectFromCheckedChange = true
if (checkedCheckableImageButtonId != -1) {
setCheckedStateForView(checkedCheckableImageButtonId, false)
}
mProtectFromCheckedChange = false
setCheckedId(child.id)
}
}
super.addView(child, index, params)
}
private fun check(id: Int) {
if (id != -1 && id == checkedCheckableImageButtonId) {
return
}
if (checkedCheckableImageButtonId != -1) {
setCheckedStateForView(checkedCheckableImageButtonId, false)
}
if (id != -1) {
setCheckedStateForView(id, true)
}
setCheckedId(id)
}
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 is AppCompatRadioButton) {
checkedView.isChecked = checked
}
}
fun clearCheck() {
check(-1)
}
fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
mOnCheckedChangeListener = listener
}
override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) {
super.onInitializeAccessibilityEvent(event)
event.className = RadioGridGroupLayout::class.java.name
}
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(info)
info.className = RadioGridGroupLayout::class.java.name
}
interface OnCheckedChangeListener {
fun onCheckedChanged(group: RadioGridGroupLayout?, 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@RadioGridGroupLayout && 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)
}
if (!child.isEnabled) child.alpha = .5F
child.setOnCheckedChangeListener(
mChildOnCheckedChangeListener
)
}
mOnHierarchyChangeListener?.onChildViewAdded(parent, child)
}
override fun onChildViewRemoved(parent: View, child: View?) {
if (parent === this@RadioGridGroupLayout && child is AppCompatRadioButton) {
child.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 version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/yoga1"
android:width="128dp"
android:height="128dp">
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorSurface"/>
<stroke android:width="2dp" android:color="?android:colorButtonNormal" />
<corners android:radius="16dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorSurface"/>
<stroke android:width="3dp" android:color="?android:colorAccent" />
<corners android:radius="16dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
<style name="GridRadioButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_margin">10dp</item>
<item name="android:background">@drawable/button_option_background_selector</item>
<item name="android:drawablePadding">16dp</item>
<item name="android:button">@null</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:padding">20dp</item>
<item name="android:textColor">@drawable/button_option_text_color_selector</item>
<item name="android:textSize">14sp</item>
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment