Skip to content

Instantly share code, notes, and snippets.

@L10n42
Created June 15, 2024 10:06
Show Gist options
  • Save L10n42/38bc989c6c5fb7ec26b0fe1e31e64984 to your computer and use it in GitHub Desktop.
Save L10n42/38bc989c6c5fb7ec26b0fe1e31e64984 to your computer and use it in GitHub Desktop.
3D Radio Button in Jetpack Compose
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
/**
* A composable function to create a customizable radio button with a convex appearance.
*
* @param selected Indicates if the radio button is selected.
* @param modifier The [Modifier] to be applied to this radio button.
* @param enabled Indicates if the radio button is enabled.
* @param colors An instance of [ConvexRadioButtonColors] to define the colors in different states.
* @param interactionSource The [MutableInteractionSource] to handle interaction events.
* @param onClick A lambda function to be called when the radio button is clicked.
*/
@Composable
fun ConvexRadioButton(
selected: Boolean,
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: ConvexRadioButtonColors = ConvexRadioButtonDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
onClick: () -> Unit
) {
val dotSize = animateDpAsState(
targetValue = if (selected) RadioButtonDotSize else 0.dp,
animationSpec = tween(durationMillis = RadioAnimationDuration)
)
val radioColor = colors.radioColorAsState(enabled, selected)
val selectableModifier = Modifier.selectable(
selected = selected,
onClick = onClick,
enabled = enabled,
role = Role.RadioButton,
interactionSource = interactionSource,
indication = rememberRipple(bounded = false, radius = RadioButtonSize)
)
val convexBorderModifier = Modifier.convexBorder(
color = radioColor.value,
shape = CircleShape,
strokeWidth = RadioStrokeWidth,
convexStyle = ConvexStyle(RadioShadowBlur, RadioShadowOffset, RadioGlareColor, RadioShadowColor)
)
Box(
modifier
.minimumInteractiveComponentSize()
.then(selectableModifier)
.size(RadioButtonSize)
.then(convexBorderModifier),
contentAlignment = Alignment.Center
) {
if (dotSize.value > 0.dp) {
Box(
modifier = Modifier
.size(dotSize.value)
.background(radioColor.value, CircleShape)
.innerShadow(CircleShape, RadioShadowColor, RadioShadowBlur, -RadioShadowOffset, -RadioShadowOffset)
.innerShadow(CircleShape, RadioGlareColor, RadioShadowBlur, RadioShadowOffset, RadioShadowOffset)
)
}
}
}
/** Defaults used for [ConvexRadioButton]. */
object ConvexRadioButtonDefaults {
/**
* Creates a [ConvexRadioButtonColors] instance with default or specified colors.
*
* @param selectedColor The color to use for the [ConvexRadioButton] when selected and enabled.
* @param unselectedColor The color to use for the [ConvexRadioButton] when unselected and enabled.
* @param disabledSelectedColor The color to use for the [ConvexRadioButton] when selected and disabled.
* @param disabledUnselectedColor The color to use for the [ConvexRadioButton] when unselected and disabled.
* @return A [ConvexRadioButtonColors] instance with the provided colors.
*/
@Composable
fun colors(
selectedColor: Color = MaterialTheme.colorScheme.primary,
unselectedColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
disabledSelectedColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = DisabledAlpha),
disabledUnselectedColor: Color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = DisabledAlpha)
): ConvexRadioButtonColors = ConvexRadioButtonColors(
selectedColor,
unselectedColor,
disabledSelectedColor,
disabledUnselectedColor
)
}
/**
* A class to define the colors used for the [ConvexRadioButton] in different states.
*
* @param selectedColor The color to be used when the [ConvexRadioButton] is selected and enabled.
* @param unselectedColor The color to be used when the [ConvexRadioButton] is unselected and enabled.
* @param disabledSelectedColor The color to be used when the [ConvexRadioButton] is selected and disabled.
* @param disabledUnselectedColor The color to be used when the [ConvexRadioButton] is unselected and disabled.
*/
class ConvexRadioButtonColors(
private val selectedColor: Color,
private val unselectedColor: Color,
private val disabledSelectedColor: Color,
private val disabledUnselectedColor: Color
) {
/**
* Determines the color of the [ConvexRadioButton] based on its state.
*
* @param enabled Whether the [ConvexRadioButton] is enabled.
* @param selected Whether the [ConvexRadioButton] is selected.
* @return The color state of the [ConvexRadioButton].
*/
@Composable
fun radioColorAsState(enabled: Boolean, selected: Boolean): State<Color> {
val target = when {
enabled && selected -> selectedColor
enabled && !selected -> unselectedColor
!enabled && selected -> disabledSelectedColor
else -> disabledUnselectedColor
}
return if (enabled) {
animateColorAsState(target, tween(durationMillis = RadioAnimationDuration))
} else {
rememberUpdatedState(target)
}
}
}
private const val RadioAnimationDuration = 100
private const val DisabledAlpha = 0.38f
private val RadioShadowOffset = 1.dp
private val RadioShadowBlur = 2.dp
private val RadioShadowColor = Color.Black.copy(0.54f)
private val RadioGlareColor = Color.White.copy(0.64f)
private val RadioButtonDotSize = 12.dp
private val RadioStrokeWidth = 3.dp
private val RadioButtonSize = 22.dp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment