How can I control the duration of AnimatedVisibility in compose? - android

I have used compose AnimatedVisibility in my project,
But the anim is to short for my need.
Is there a related API?

Here is an example taken from codelabs. You can add your own animation specs with the durationMillis of each animation, one for entering, and one for exiting:
AnimatedVisibility(
visible = shown,
enter = slideInVertically(
// Enters by sliding down from offset -fullHeight to 0.
initialOffsetY = { fullHeight -> -fullHeight },
animationSpec = tween(durationMillis = 150, easing = LinearOutSlowInEasing)
),
exit = slideOutVertically(
// Exits by sliding up from offset 0 to -fullHeight.
targetOffsetY = { fullHeight -> -fullHeight },
animationSpec = tween(durationMillis = 250, easing = FastOutLinearInEasing)
)
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Text(
text = stringResource(R.string.edit_message),
modifier = Modifier.padding(16.dp)
)
}
}

Well this is one way
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun AnimatedVisibilityMark2(content: #Composable () -> Unit, visible: Boolean, durationMillis: Int) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(
animationSpec = keyframes {
this.durationMillis = durationMillis
}
),
exit = fadeOut(
animationSpec = keyframes {
this.durationMillis = durationMillis
}
)
){
content()
}
}

Related

Jetpack Compose: How to create multi-item FAB with animation?

I'm trying to create a multi-item floating action button with the following animation:
I created a multi-item floating action button but I could not implement the intended animation.
I have FilterFabMenuButton composable that I show as a menu item :
FilterFabMenuButton
#Composable
fun FilterFabMenuButton(
item: FilterFabMenuItem,
onClick: (FilterFabMenuItem) -> Unit,
modifier: Modifier = Modifier
) {
FloatingActionButton(
modifier = modifier,
onClick = {
onClick(item)
},
backgroundColor = colorResource(
id = R.color.primary_color
)
) {
Icon(
painter = painterResource(item.icon), contentDescription = null, tint = colorResource(
id = R.color.white
)
)
}
}
I have FilterFabMenuLabel composable which is a label for FilterFabMenuButton:
FilterFabMenuLabel
#Composable
fun FilterFabMenuLabel(
label: String,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier,
shape = RoundedCornerShape(6.dp),
color = Color.Black.copy(alpha = 0.8f)
) {
Text(
text = label, color = Color.White,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 2.dp),
fontSize = 14.sp,
maxLines = 1
)
}
}
I have FilterFabMenuItem composable which is a Row that contains FilterFabMenuLabel and FilterFabMenuButton composables:
FilterFabMenuItem
#Composable
fun FilterFabMenuItem(
menuItem: FilterFabMenuItem,
onMenuItemClick: (FilterFabMenuItem) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
//label
FilterFabMenuLabel(label = menuItem.label)
//fab
FilterFabMenuButton(item = menuItem, onClick = onMenuItemClick)
}
}
I have FilterFabMenu composable which is a Column that shows menu items:
FilterFabMenu
#Composable
fun FilterFabMenu(
visible: Boolean,
items: List<FilterFabMenuItem>,
modifier: Modifier = Modifier
) {
val enterTransition = remember {
expandVertically(
expandFrom = Alignment.Bottom,
animationSpec = tween(150, easing = FastOutSlowInEasing)
) + fadeIn(
initialAlpha = 0.3f,
animationSpec = tween(150, easing = FastOutSlowInEasing)
)
}
val exitTransition = remember {
shrinkVertically(
shrinkTowards = Alignment.Bottom,
animationSpec = tween(150, easing = FastOutSlowInEasing)
) + fadeOut(
animationSpec = tween(150, easing = FastOutSlowInEasing)
)
}
AnimatedVisibility(visible = visible, enter = enterTransition, exit = exitTransition) {
Column(
modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
items.forEach { menuItem ->
FilterFabMenuItem(
menuItem = menuItem,
onMenuItemClick = {}
)
}
}
}
}
I have FilterFab composable that expands/collapses FilterMenu:
FilterFab
#Composable
fun FilterFab(
state: FilterFabState,
rotation:Float,
onClick: (FilterFabState) -> Unit,
modifier: Modifier = Modifier
) {
FloatingActionButton(
modifier = modifier
.rotate(rotation),
elevation = FloatingActionButtonDefaults.elevation(defaultElevation = 0.dp),
onClick = {
onClick(
if (state == FilterFabState.EXPANDED) {
FilterFabState.COLLAPSED
} else {
FilterFabState.EXPANDED
}
)
},
backgroundColor = colorResource(
R.color.primary_color
),
shape = CircleShape
) {
Icon(
painter = painterResource(R.drawable.fab_add),
contentDescription = null,
tint = Color.White
)
}
}
Last but not least, I have a FilterView composable which is a Column that contains FilterFabMenu and FilterFab composables:
FilterView
#SuppressLint("UnusedTransitionTargetStateParameter")
#Composable
fun FilterView(
items: List<FilterFabMenuItem>,
modifier: Modifier = Modifier
) {
var filterFabState by rememberSaveable() {
mutableStateOf(FilterFabState.COLLAPSED)
}
val transitionState = remember {
MutableTransitionState(filterFabState).apply {
targetState = FilterFabState.COLLAPSED
}
}
val transition = updateTransition(targetState = transitionState, label = "transition")
val iconRotationDegree by transition.animateFloat({
tween(durationMillis = 150, easing = FastOutSlowInEasing)
}, label = "rotation") {
if (filterFabState == FilterFabState.EXPANDED) 230f else 0f
}
Column(
modifier = modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(16.dp,Alignment.Bottom)
) {
FilterFabMenu(items = items, visible = filterFabState == FilterFabState.EXPANDED)
FilterFab(
state = filterFabState,
rotation = iconRotationDegree, onClick = { state ->
filterFabState = state
})
}
}
This produces the following result:
expandVertically in your enterTransition is not the correct approach for this kind of animation. Per documentation, it animates a clip revealing the content of the animated item from top to bottom, or vice versa. You apply this animation to the entire column (so all items at once), resulting in the gif you have shown us.
Instead, you should use a different enter/exit animation type, maybe a custom animation where you work with the item scaling to emulate the "pop in" effect like such:
scaleFactor.animateTo(2f, tween(easing = FastOutSlowInEasing, durationMillis = 50))
scaleFactor.animateTo(1f, tween(easing = FastOutSlowInEasing, durationMillis = 70))
(the scaleFactor is an animatabale of type Animatable<Float, AnimationVector1D> in this instance).
Then you create such an animatable for each of the column items, i.e. your menu items. After that, just run the animations in a for loop for each menu item inside a coroutine scope (since compose animations are suspend by default, they will run in sequence, use async/awaitAll if you want to do it in parallel).
Also, don't forget to put your animatabales in remember {}, then just use the values you are animating like scaleFactor inside modifiers of your column items, and trigger them inside a LaunchedEffect when you click to expand/close the menu.

Jetpack Compose Animation skips to target value immediately

I'm trying to achieve a smooth animation of a simple round timer. Like this, but smoother
However it just skips to targetValue immediately and that's it there's no animation at all. I'm trying to do it like this:
#Composable
private fun SampleTimer(duration: Int, modifier: Modifier = Modifier) {
var animatedPercentage by remember { mutableStateOf(1f) }
LaunchedEffect(Unit) {
animate(
initialValue = 1f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
tween(
durationMillis = duration.seconds.inWholeMilliseconds.toInt(),
easing = LinearEasing,
),
),
) { value, _ ->
animatedPercentage = value
}
}
val arcColor = MaterialTheme.colors.primaryVariant
Canvas(
modifier = modifier,
) {
drawArc(
color = arcColor,
useCenter = true,
startAngle = -90f,
sweepAngle = -360f * animatedPercentage,
)
}
}
Why does this happen, what am I missing here?
You can use an Animatable state. The angle will animate from 0–360°.
Something like:
val angle = remember {
Animatable(0f)
}
LaunchedEffect(angle) {
launch {
angle.animateTo(360f, animationSpec =
infiniteRepeatable(
tween(
durationMillis = 5000,
easing = LinearEasing,
),
)
)
}
}
val arcColor = Red
Canvas(
modifier = Modifier.size(100.dp),
) {
drawArc(
color = arcColor,
useCenter = true,
startAngle = -90f,
sweepAngle = -angle.value,
)
}
The problem was that the animations were turned off in developer settings on my device, and I forgot that

push an animation in composable function to start again whenever I click a button/ JetPack/ Kotlin

**every time the user click the button i want to show different icon inside the box and the transition should start again from its initial state **
fun card(iconColor: Color){
val animateShape = remember { Animatable(50f) }
Card(
modifier = Modifier
.size(100.dp)
.padding(5.dp)
.border(width = Dp(animateShape.value), White, shape = CircleShape),
shape = CircleShape,
elevation = 5.dp,
backgroundColor = BlueGrey,
) {
Icon(
imageVector = Icons.Default.DoneAll,
contentDescription = null,
tint = iconColor,
modifier = Modifier
.padding(12.dp)
)
}
LaunchedEffect(iconColor) {
animateShape.animateTo(
targetValue = 2f,
animationSpec = repeatable(
animation = tween(
durationMillis = 3000,
easing = LinearEasing,
delayMillis = 500
),
iterations = 1
)
)
}
}```
I'm not quite sure, but are you looking for something like this?, every click it changes the Icon with scaleIn/scaleOut animation
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun SwitchIconOnClick() {
var iconState by remember { mutableStateOf("State_1") }
AnimatedContent(
targetState = iconState,
contentAlignment = Alignment.Center,
transitionSpec = {
scaleIn(animationSpec = tween(durationMillis = 200)) with
scaleOut(animationSpec = tween(durationMillis = 200))
}
) { state ->
Box(
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
.clickable {
when (state) {
"State_1" -> {
iconState = "State_2"
}
"State_2" -> {
iconState = "State_3"
}
"State_3" -> {
iconState = "State_1"
}
}
},
contentAlignment = Alignment.Center
) {
Icon(imageVector = when (state) {
"State_1" -> {
Icons.Rounded.NotificationAdd
}
"State_2" -> {
Icons.Rounded.ClosedCaption
}
"State_3" -> {
Icons.Rounded.Delete
}
else -> {
Icons.Rounded.DataArray
}
},
contentDescription = null
)
}
}
}

Compose - AnimatedVisibility and offset

When adding an enter and exit transition to AnimatedVisibility, if the composable animated has an offset modifier, the transition is not visible:
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(1000)),
exit = fadeOut(animationSpec = tween(1000))
) {
Text(
text = "Hello, world!", modifier = Modifier
// This will make the transition not working
//.offset(100.dp, 0.dp)
)
}
Button(onClick = { visible = !visible }) {
Text("Click Me")
}
This is the animation without the offset:
and this is the animation with the offset:
Is there a way to make it work?
Don't out the offset modifier onto the Text. Put it onto AnimatedVisibility instead:
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(1000)),
exit = fadeOut(animationSpec = tween(1000)),
modifier = Modifier.offset(100.dp, 0.dp)
) {
Text(text = "Hello, world!")
}

Sliding an item next to the expanding animation

I've got the following animation:
The problem is:
When animation's starting the search icon (magnifier) slides immediately to the left of the screen.
When the search bar is folding back the icon moves smoothly and near the end speed up.
What I want to achieve here is to make this icon slides more smoothly for a better experience.
Is there any way to achieve that?
Code responsible for animation:
IconButton(onClick = {
isSearchEnabled = !isSearchEnabled
}) {
Icon(Icons.Default.Search, "search")
}
AnimatedVisibility(
visible = isSearchEnabled,
enter = fadeIn(
animationSpec = tween(durationMillis = 300)
) + slideInHorizontally(
initialOffsetX = { it / 2 },
animationSpec = tween(durationMillis = 700)
),
exit = fadeOut(
animationSpec = tween(300, easing = FastOutLinearInEasing)
) + shrinkHorizontally(
shrinkTowards = Alignment.End,
animationSpec = tween(durationMillis = 700, easing = FastOutLinearInEasing)
)
) {
TextField(
modifier = Modifier.padding(end = 16.dp),
shape = RoundedCornerShape(10.dp),
value = text,
onValueChange = { text = it; onValueChange(it) })
}
This would expand and shrink the search bar,
#ExperimentalAnimationApi
#Composable
fun ExpandableSearchbar() {
var text by remember {
mutableStateOf("")
}
var isSearchEnabled by remember {
mutableStateOf(false)
}
val slow = 700
val fast = 300
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.background(Color(0xFFE2E2E2))
.height(120.dp),
) {
IconButton(
onClick = {
isSearchEnabled = !isSearchEnabled
},
) {
Icon(Icons.Default.Search, "search")
}
AnimatedVisibility(
visible = isSearchEnabled,
enter = fadeIn(
animationSpec = tween(durationMillis = fast)
) + expandHorizontally(
expandFrom = Alignment.End,
animationSpec = tween(
durationMillis = slow,
easing = FastOutLinearInEasing,
)
),
exit = fadeOut(
animationSpec = tween(
durationMillis = slow,
easing = FastOutLinearInEasing,
)
) + shrinkHorizontally(
shrinkTowards = Alignment.End,
animationSpec = tween(
durationMillis = slow,
easing = FastOutLinearInEasing,
)
)
) {
TextField(
modifier = Modifier.padding(end = 16.dp),
shape = RoundedCornerShape(10.dp),
value = text,
onValueChange = {
text = it
},
)
}
}
}

Categories

Resources