I want to animate the placeholder of my SearchBar Composable just like the Google Play Store searchbar placeholder. I tried to do it, but it does not seem to be as subtle as the Google Play Store.
This is what I want to achieve,
This is what I have done so far,
Here is my code,
var showInitialPlaceholder by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
delay(1000)
showInitialPlaceholder = false
}
SearchBar(
...
placeholder = {
Crossfade(targetState = showInitialPlaceholder) { isInitial ->
if (isInitial) {
Text("Google Play")
} else {
Text("Search...")
}
}
}
...
)
I've crated simple searchBox example. I used two AnimatedVisibility
blocks for the placeholders because in the desired gif you can see second placeholder appearing after the initial one is hidden, so Crossfade
is not the best option.
Basically there is:
slideIn
animation of SearchBoxfadeOut
animation of initial placeholderfadeIn
animation of final placeholderUnfortunately i don't have good eyes when it comes to these little details so i debugged it with Window animation scale turned on and implement it too. Maybe try to play with animation durations, delays and choose desired easing. Try this and see how it will work for you, hope it helps at least a little.
@Composable
fun AnimatedSearchBox(modifier: Modifier = Modifier) {
val context = LocalContext.current
//scale for debugging with window scale option
val animScale = remember {
Settings.Global.getFloat(
context.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f
)
}
//Searchbar slide animation
var isSearchBarVisible by remember { mutableStateOf(false) }
//Initial "Google Play" visibility
var isInitialVisible by remember { mutableStateOf(true) }
//Placeholder visibility
var isSecondPhaseVisible by remember { mutableStateOf(false) }
//Searchbar properties
var isActive by remember { mutableStateOf(false) }
var query by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
//Initial delay for searchbar slide animation
delay((animScale * 1000).toLong())
isSearchBarVisible = true
//Delay before hiding initial placeholder
delay((1000 * animScale.toLong()))
isInitialVisible = false
//Delay before showing placeholder, must be lower than initial placeholder animation duration
//To make it more smooth
delay(timeMillis = (250 * animScale).toLong())
isSecondPhaseVisible = true
}
AnimatedVisibility(
visible = isSearchBarVisible,
enter = fadeIn() + slideInVertically { it },
) {
SearchBar(
modifier = modifier,
query = query,
onQueryChange = { query = it },
active = isActive,
placeholder = {
AnimatedVisibility(
visible = isInitialVisible,
exit = fadeOut(
animationSpec = tween(
durationMillis = 400,
easing = EaseInOutExpo
)
)
) {
Text("Google Play")
}
AnimatedVisibility(
visible = isSecondPhaseVisible,
enter = fadeIn(
animationSpec = tween(
durationMillis = 200,
easing = EaseInOutExpo
)
)
) {
Text("Search...")
}
},
onActiveChange = {
isActive = it
},
onSearch = {},
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.outline_search_24),
contentDescription = null
)
},
trailingIcon = {
Icon(
painter = painterResource(id = R.drawable.baseline_checklist_rtl_24),
contentDescription = null
)
},
content = {
}
)
}
}