Skip to content

Instantly share code, notes, and snippets.

@Dev-Husnain
Last active April 17, 2025 19:07
Show Gist options
  • Select an option

  • Save Dev-Husnain/1f22903e3afe8a099b076fe264434080 to your computer and use it in GitHub Desktop.

Select an option

Save Dev-Husnain/1f22903e3afe8a099b076fe264434080 to your computer and use it in GitHub Desktop.
CustomRadioView & CustomRadioGroup (Customize you views).
class CustomRadioGroup @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) {
private var checkedView: CustomRadioView? = null
private var listener: ((CustomRadioView) -> Unit)? = null
fun setOnCheckedChangeListener(listener: (CustomRadioView) -> Unit) {
this.listener = listener
}
fun onChildChecked(checked: CustomRadioView) {
if (checkedView == checked) return
checkedView?.setChecked(false)
checkedView = checked
checked.setChecked(true)
listener?.invoke(checked)
}
fun clearSelection() {
checkedView?.setChecked(false)
checkedView = null
}
fun getCheckedRadioView(): CustomRadioView? = checkedView
fun getCheckedText(): String? = checkedView?.getText()
fun getCheckedIndex(): Int = indexOfChild(checkedView)
}
class CustomRadioView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private var isChecked = false
private var defaultChecked = false
private var textView: AppCompatTextView
// Style properties
private var selectedCornerRadius = 0f
private var unselectedCornerRadius = 0f
private var selectedBgColor = 0
private var unselectedBgColor = 0
private var selectedStrokeWidth = 0
private var unselectedStrokeWidth = 0
private var selectedStrokeColor = 0
private var unselectedStrokeColor = 0
private var selectedTextColor = 0
private var unselectedTextColor = 0
private var selectedTextSize = 0f
private var unselectedTextSize = 0f
private var selectedFontStyle = Typeface.NORMAL
private var unselectedFontStyle = Typeface.NORMAL
init {
LayoutInflater.from(context).inflate(R.layout.view_custom_radio, this, true)
textView = findViewById(R.id.radioText)
context.theme.obtainStyledAttributes(attrs, R.styleable.CustomRadioView, 0, 0).apply {
try {
selectedCornerRadius =
getDimension(R.styleable.CustomRadioView_cornerRadiusSelected, 0f)
unselectedCornerRadius =
getDimension(R.styleable.CustomRadioView_cornerRadiusUnselected, 0f)
selectedBgColor = getColor(R.styleable.CustomRadioView_backgroundColorSelected, 0)
unselectedBgColor =
getColor(R.styleable.CustomRadioView_backgroundColorUnselected, 0)
selectedStrokeWidth =
getDimensionPixelSize(R.styleable.CustomRadioView_strokeWidthSelected, 0)
unselectedStrokeWidth =
getDimensionPixelSize(R.styleable.CustomRadioView_strokeWidthUnselected, 0)
selectedStrokeColor = getColor(R.styleable.CustomRadioView_strokeColorSelected, 0)
unselectedStrokeColor =
getColor(R.styleable.CustomRadioView_strokeColorUnselected, 0)
selectedTextColor = getColor(R.styleable.CustomRadioView_textColorSelected, 0)
unselectedTextColor = getColor(R.styleable.CustomRadioView_textColorUnselected, 0)
selectedTextSize =
getDimension(R.styleable.CustomRadioView_textSizeSelected, textView.textSize)
unselectedTextSize =
getDimension(R.styleable.CustomRadioView_textSizeUnselected, textView.textSize)
defaultChecked = getBoolean(R.styleable.CustomRadioView_defaultChecked, false)
selectedFontStyle =
getInt(R.styleable.CustomRadioView_fontStyleSelected, Typeface.NORMAL)
unselectedFontStyle =
getInt(R.styleable.CustomRadioView_fontStyleUnselected, Typeface.NORMAL)
val text = getString(R.styleable.CustomRadioView_android_text)
textView.text = text ?: ""
} finally {
recycle()
}
}
post {
setDefaultChecked(defaultChecked)
}
setOnClickListener {
if (!isChecked) {
setChecked(true)
} else {
(parent as? CustomRadioGroup)?.onChildChecked(this)
}
}
refreshView()
}
private fun refreshView() {
val drawable = GradientDrawable().apply {
cornerRadius = if (isChecked) selectedCornerRadius else unselectedCornerRadius
setColor(if (isChecked) selectedBgColor else unselectedBgColor)
setStroke(
if (isChecked) selectedStrokeWidth else unselectedStrokeWidth,
if (isChecked) selectedStrokeColor else unselectedStrokeColor
)
}
background = drawable
textView.setTextColor(if (isChecked) selectedTextColor else unselectedTextColor)
textView.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
if (isChecked) selectedTextSize else unselectedTextSize
)
textView.setTypeface(null, if (isChecked) selectedFontStyle else unselectedFontStyle)
}
fun setChecked(checked: Boolean) {
if (checked == isChecked) return
isChecked = checked
refreshView()
(parent as? CustomRadioGroup)?.onChildChecked(this)
}
fun getText(): String = textView.text.toString()
private fun setDefaultChecked(defaultChecked: Boolean) {
isChecked = defaultChecked
post { refreshView() }
if (defaultChecked) {
(parent as? CustomRadioGroup)?.onChildChecked(this)
}
}
fun isChecked(): Boolean = isChecked
}
📌 CustomRadioView & CustomRadioGroup (Kotlin – Android)
✅ Overview
CustomRadioView is a highly customizable view designed to behave like a RadioButton, with support for rich visual customization such as:
->different corner radius
->Background color
->Stroke
->Text style
->Text size and font style
It is designed to work inside a CustomRadioGroup that manages exclusive selection behavior — only one item is selected at a time, similar to native RadioGroup.
🎯 Features
✅ Fully customizable for selected and unselected states:
Corner radius
Background color
Stroke color & width
Text color
Text size
Font style (bold, italic, normal)
Elevation
Default selection
✅ Use standard Android attributes:
android:text
android:elevation
✅ Can contain other child views inside it — built using ConstraintLayout, FrameLayout, or similar.
✅ Easily used in XML and programmatically controlled in Kotlin.
🧩 Attributes
defaultChecked | boolean | Marks this view as selected by default
selectedCornerRadius | dimension | Corner radius when selected
unselectedCornerRadius | dimension | Corner radius when unselected
selectedBackgroundColor | color | Background when selected
unselectedBackgroundColor | color | Background when unselected
selectedStrokeColor | color | Stroke when selected
unselectedStrokeColor | color | Stroke when unselected
selectedStrokeWidth | dimension | Stroke width when selected
unselectedStrokeWidth | dimension | Stroke width when unselected
selectedTextColor | color | Text color when selected
unselectedTextColor | color | Text color when unselected
selectedTextSize | dimension | Text size when selected
unselectedTextSize | dimension | Text size when unselected
selectedFontStyle | integer (0:normal, 1:bold, 2:italic) | Font style when selected
unselectedFontStyle | integer | Font style when unselected
//Add below to you xml file
<com.all.languages.text.voice.image.translation.views.CustomRadioGroup
android:id="@+id/radioGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/_10sdp"
app:layout_constraintBottom_toTopOf="@id/btnContinue"
app:layout_constraintEnd_toEndOf="@id/btnContinue"
app:layout_constraintStart_toStartOf="@id/btnContinue">
<com.all.languages.text.voice.image.translation.views.CustomRadioView
android:id="@+id/radioWeekly"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:elevation="0.5dp"
android:padding="@dimen/_7sdp"
app:backgroundColorSelected="#D0E5FF"
app:backgroundColorUnselected="#F4F3F8"
app:cornerRadiusSelected="@dimen/_10sdp"
app:cornerRadiusUnselected="@dimen/_10sdp"
app:defaultChecked="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:strokeColorSelected="#1A73E8"
app:strokeColorUnselected="#ADADAD"
app:strokeWidthSelected="@dimen/_1sdp"
app:strokeWidthUnselected="@dimen/_1sdp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvWeekly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_10sdp"
android:text="@string/weekly"
android:textColor="@color/black"
android:textSize="@dimen/_13ssp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvPriceWeekly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/_10sdp"
android:text="@string/dots"
android:textColor="@color/black"
android:textSize="@dimen/_14ssp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvPayWeekly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_10sdp"
android:text="@string/pay_for_weekly"
android:textColor="@color/colorAB"
android:textSize="@dimen/_12ssp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvWeekly" />
</com.all.languages.text.voice.image.translation.views.CustomRadioView>
<com.all.languages.text.voice.image.translation.views.CustomRadioView
android:id="@+id/radioMonthly"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/_10sdp"
android:elevation="0.5dp"
android:padding="@dimen/_7sdp"
app:backgroundColorSelected="#D0E5FF"
app:backgroundColorUnselected="#F4F3F8"
app:cornerRadiusSelected="@dimen/_10sdp"
app:cornerRadiusUnselected="@dimen/_10sdp"
app:defaultChecked="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/radioWeekly"
app:strokeColorSelected="#1A73E8"
app:strokeColorUnselected="#ADADAD"
app:strokeWidthSelected="@dimen/_1sdp"
app:strokeWidthUnselected="@dimen/_1sdp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvMonthly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_10sdp"
android:text="@string/monthly"
android:textColor="@color/black"
android:textSize="@dimen/_13ssp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvPriceMonthly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/_10sdp"
android:text="@string/dots"
android:textColor="@color/black"
android:textSize="@dimen/_14ssp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvPayMonthly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_10sdp"
android:text="@string/pay_for_monthly"
android:textColor="@color/colorAB"
android:textSize="@dimen/_12ssp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvMonthly" />
</com.all.languages.text.voice.image.translation.views.CustomRadioView>
<com.all.languages.text.voice.image.translation.views.CustomRadioView
android:id="@+id/radioYearly"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/_10sdp"
android:elevation="0.5dp"
android:padding="@dimen/_7sdp"
app:backgroundColorSelected="#D0E5FF"
app:backgroundColorUnselected="#F4F3F8"
app:cornerRadiusSelected="@dimen/_10sdp"
app:cornerRadiusUnselected="@dimen/_10sdp"
app:defaultChecked="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/radioMonthly"
app:strokeColorSelected="#1A73E8"
app:strokeColorUnselected="#ADADAD"
app:strokeWidthSelected="@dimen/_1sdp"
app:strokeWidthUnselected="@dimen/_1sdp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvYearly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_10sdp"
android:text="@string/yearly"
android:textColor="@color/black"
android:textSize="@dimen/_13ssp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvPriceYearly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/_10sdp"
android:text="@string/dots"
android:textColor="@color/black"
android:textSize="@dimen/_14ssp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvPayYearly"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_10sdp"
android:text="@string/pay_for_yearly"
android:textColor="@color/colorAB"
android:textSize="@dimen/_12ssp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvYearly" />
</com.all.languages.text.voice.image.translation.views.CustomRadioView>
</com.all.languages.text.voice.image.translation.views.CustomRadioGroup>
//add below to you layouts folder
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/radioText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="8dp"
android:gravity="center"
android:textColor="@android:color/black"
android:textSize="16sp" />
</merge>
//add below to your attrs folder
<declare-styleable name="CustomRadioView">
<attr name="android:text" />
<attr name="defaultChecked" format="boolean" />
<attr name="cornerRadiusSelected" format="dimension" />
<attr name="cornerRadiusUnselected" format="dimension" />
<attr name="backgroundColorSelected" format="color" />
<attr name="backgroundColorUnselected" format="color" />
<attr name="strokeWidthSelected" format="dimension" />
<attr name="strokeWidthUnselected" format="dimension" />
<attr name="strokeColorSelected" format="color" />
<attr name="strokeColorUnselected" format="color" />
<attr name="textColorSelected" format="color" />
<attr name="textColorUnselected" format="color" />
<attr name="textSizeSelected" format="dimension" />
<attr name="textSizeUnselected" format="dimension" />
<attr name="fontStyleSelected" format="enum">
<enum name="normal" value="0" />
<enum name="bold" value="1" />
<enum name="italic" value="2" />
</attr>
<attr name="fontStyleUnselected" format="enum">
<enum name="normal" value="0" />
<enum name="bold" value="1" />
<enum name="italic" value="2" />
</attr>
</declare-styleable>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment