Johan Paul
Johan Paul

Reputation: 2456

How to animate the progress of a LinearProgressIndicator in Jetpack Compose?

I have a LinearProgressIndicator that correctly shows the current progress. Now I would like the progress to be animated and I thought this would be super simple with animateFloatAsState. Well, not sure what I am not grokking, but with the code below, the progress is not animated, but immediately shown at the correct place.

@Composable
fun MyIndicator() {
    val progressAnimDuration = 1500
    val progressAnimation by animateFloatAsState(
        targetValue = 0.35f
        animationSpec = tween(durationMillis = progressAnimDuration, easing = FastOutSlowInEasing)
    )
     LinearProgressIndicator(
        progress = progressAnimation,
        ...
        modifier = Modifier           
           .fillMaxWidth()
           .clip(RoundedCornerShape(20.dp)). // Rounded edges
    )
}

So whenever I show that component on the screen, I would expect the progress to be animated to its correct progress state. But instead, the progress is not animated but immediately shown at the correct progress state.

What's wrong here? :)

Upvotes: 18

Views: 16662

Answers (7)

Dani Chuks
Dani Chuks

Reputation: 528

I had a similar problem, in my case I used LaunchedEffect to update the progress from zero to the desired value

@Composable
fun AnimatedLinearProgressIndicator(
    indicatorProgress: Float,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
) {
    var progress by remember { mutableStateOf(0F) }
    val progressAnimDuration = 1_500
    val progressAnimation by animateFloatAsState(
        targetValue = progress,
        animationSpec = tween(durationMillis = progressAnimDuration, easing = FastOutSlowInEasing),
    )
    LinearProgressIndicator(
        progress = { progressAnimation },
        modifier = Modifier
            .fillMaxWidth()
            .clip(RoundedCornerShape(8.dp)),
    )
    LaunchedEffect(lifecycleOwner) {
        progress = indicatorProgress
    }
}

Upvotes: 30

Arun Ajayan
Arun Ajayan

Reputation: 31

@Composable
    fun MyIndicator(indicatorProgress: Float) {
        var progress by remember { mutableStateOf(0f) }
        val progressAnimDuration = 1500
        val progressAnimation by animateFloatAsState(
            targetValue = progress,
            animationSpec = tween(durationMillis = progressAnimDuration, easing = FastOutSlowInEasing)
        )
        Column(
            modifier = Modifier
                .background(Color.White)
                .fillMaxSize(),
            verticalArrangement = Arrangement.Center
        ) {
            LinearProgressIndicator(
                modifier = Modifier
                    .fillMaxWidth()
                    .clip(RoundedCornerShape(20.dp)),
                progress = progressAnimation
            )
        }
        LaunchedEffect(indicatorProgress) {
            progress = indicatorProgress
        }
    }

A small modification made in the answer by @Dani Chucks.

Upvotes: 1

Manuel Gonzalez
Manuel Gonzalez

Reputation: 1

i solved it by having a mutable state at 0 then make it null at the end of the code to trigger recomposition

{
var starterProgressValue by remember { mutableStateOf<Float?>(0f) }
val actualProgress = 1f

AnimatedProgressBar(
    progress = starterProgressValue ?: actualProgress,
    duration = 3000,
    modifier = Modifier
        .align(Alignment.CenterStart)
        .height(12.dp)
        .fillMaxWidth()
)

starterProgressValue = null

}

Upvotes: 0

canvas
canvas

Reputation: 11

This is how I did it. I use progress indicator to simulate a loading effect, then jump to the login activity.

var currentProgress by remember { mutableFloatStateOf(0f) }
val context = LocalContext.current

// jump to Login Activity after animation
val currentPercentage by animateFloatAsState(
    targetValue = currentProgress,
    animationSpec =  tween( durationMillis = 1000, easing = LinearEasing),
    label = "progress",
    finishedListener = { _ -> context.startActivity(Intent(context, LoginActivity3::class.java)) }
)

LaunchedEffect(key1 = Unit){
    currentProgress = 1f
}

Box(modifier = Modifier.fillMaxSize(),contentAlignment = Alignment.Center)
{
    Column(horizontalAlignment = Alignment.CenterHorizontally)
    {
        LinearProgressIndicator(
            modifier = Modifier.width(220.dp).height(5.dp),
            progress = currentPercentage,
            color = colorResource(R.color.green_500)
        )
    }
}

Upvotes: 1

padPad
padPad

Reputation: 199

You case use the Animatable, but you need the LaunchEffect to trigger the animation

  val percent = remember { Animatable(0f) }


  LaunchedEffect(key1 = Unit, block = {
        percent.animateTo(
            targetValue = yourPercentage ,
            animationSpec = tween(
                durationMillis = (1000 * (1f - percent.value)).toInt(),
                easing = FastOutLinearInEasing
            )
        )
    })

     LinearProgressIndicator(
            progress = percent.value
        )

Upvotes: 1

Gk Mohammad Emon
Gk Mohammad Emon

Reputation: 6938

In my case, it was worked -

 var animationPlayed by remember {
            mutableStateOf(false)
        }
        val currentPercentage = animateFloatAsState(targetValue =
            if(animationPlayed) percentage else 0f,
            animationSpec = tween( durationMillis = 4000)
        )
        LaunchedEffect(key1 = true){
            animationPlayed=true
        }
    
    LinearProgressIndicator(...progress = currentPercentage)

Upvotes: 0

Shepherd
Shepherd

Reputation: 1

I found a very important parameter visibilityThreshold in the animateFloatAsState, you have to set it to 0.001f to make it work.

Upvotes: 0

Related Questions