Created
July 27, 2025 12:05
-
-
Save ardakazanci/3f55b595043274e6c61fcb69ea9c4340 to your computer and use it in GitHub Desktop.
Keyboard Animation Insets with Jetpack Compose
This file contains hidden or 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
@Composable | |
fun KeyboardWindowInsets(modifier: Modifier = Modifier) { | |
val coroutineScope = rememberCoroutineScope() | |
val verticalOffset = remember { Animatable(0f) } | |
var email by remember { mutableStateOf("") } | |
var sliderValue by remember { mutableFloatStateOf(1f) } | |
val isValidEmail = remember(email) { | |
Patterns.EMAIL_ADDRESS.matcher(email).matches() | |
} | |
val showCard = email.isNotEmpty() | |
val backgroundColor by animateColorAsState( | |
if (isValidEmail) Color(0xFF00C853) else Color(0xFFD32F2F), | |
label = "background" | |
) | |
val icon = if (isValidEmail) Icons.Default.CheckCircle else Icons.Default.Error | |
val baseMessage = if (isValidEmail) "Looks great!" else "Oops! Check your email." | |
val maxChars = 300 | |
val adjustedMessage = buildString { | |
repeat(sliderValue.toInt()) { | |
append(baseMessage) | |
} | |
}.take(maxChars) | |
Column( | |
modifier | |
.fillMaxSize() | |
.background(Color(0xFF121212)) | |
.padding(16.dp) | |
) { | |
Text( | |
"What's your email?", | |
fontSize = 20.sp, | |
color = Color.White, | |
fontWeight = FontWeight.Bold | |
) | |
Spacer(Modifier.height(12.dp)) | |
TextField( | |
value = email, | |
onValueChange = { email = it }, | |
placeholder = { Text("[email protected]") }, | |
modifier = Modifier | |
.fillMaxWidth() | |
.background(Color.White, RoundedCornerShape(12.dp)) | |
) | |
Spacer(Modifier.height(16.dp)) | |
Text( | |
text = "Message intensity: ${sliderValue.toInt()}x", | |
color = Color.White | |
) | |
Slider( | |
value = sliderValue, | |
onValueChange = { sliderValue = it }, | |
valueRange = 1f..10f, | |
steps = 3, | |
modifier = Modifier.fillMaxWidth() | |
) | |
Box( | |
modifier = Modifier | |
.fillMaxSize() | |
.layout { measurable, constraints -> | |
layout(constraints.maxWidth, constraints.maxHeight) { | |
val sliderInfluence = sliderValue | |
val placeable = measurable.measure(constraints) | |
val ime = WindowInsetsRulers.Ime | |
val animation = ime.getAnimation(this) | |
val height = constraints.maxHeight.toFloat() | |
val sourceBottom = animation.source.bottom.current(height) | |
val targetBottom = animation.target.bottom.current(height) | |
val targetY = if (!animation.isVisible || sourceBottom < targetBottom) { | |
0f | |
} else if (animation.isAnimating) { | |
targetBottom - placeable.height | |
} else { | |
ime.current.bottom.current(height) - placeable.height | |
} | |
if (targetY != verticalOffset.targetValue) { | |
coroutineScope.launch { | |
verticalOffset.animateTo(targetY) | |
} | |
} | |
placeable.place(0, verticalOffset.value.roundToInt()) | |
} | |
}, | |
contentAlignment = Alignment.BottomCenter | |
) { | |
[email protected]( | |
visible = showCard, | |
enter = fadeIn(animationSpec = tween(400)) + scaleIn(), | |
exit = fadeOut(animationSpec = tween(300)) + scaleOut() | |
) { | |
Box( | |
Modifier | |
.graphicsLayer { | |
shadowElevation = 16f | |
shape = RoundedCornerShape(24.dp) | |
clip = true | |
} | |
.background(backgroundColor) | |
.padding(horizontal = 24.dp, vertical = 16.dp) | |
) { | |
Row(verticalAlignment = Alignment.CenterVertically) { | |
Icon( | |
imageVector = icon, | |
contentDescription = null, | |
tint = Color.White, | |
modifier = Modifier.size(24.dp) | |
) | |
Spacer(Modifier.width(12.dp)) | |
Text( | |
text = adjustedMessage, | |
color = Color.White, | |
fontSize = 16.sp, | |
fontWeight = FontWeight.SemiBold | |
) | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment