Instantly share code, notes, and snippets.
L10n42/ConvexRadioButton.kt Secret
Created
June 15, 2024 10:06
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save L10n42/38bc989c6c5fb7ec26b0fe1e31e64984 to your computer and use it in GitHub Desktop.
3D Radio Button in Jetpack Compose
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 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