Friday, April 16, 2021

jetpack compose - Rotate animation

Compose Roate Animation
Animation plays a vital role in an android application lifecycle. Today’s mobile applications are built-in with full of animations. Most of those animations are system-generated such as a Button click effect. Android app developers need to create some animations per their application requirements. There are many types of animations that exist in the jetpack compose library. Today we will discuss here rotate animation which means object rotation animation.

In the following kotlin example code, we rotate an Icon object on a Button click event. We can display a rotated icon without animating it by using its modifier object’s ‘rotate’ element. When we put a value between 0 to 360, it will display the object as rotated to the specified degrees. So this modifier’s ‘rotate()’ element will help us to create a rotate animation for a widget. Somehow we need to change the value of the ‘degrees’ argument of the 'rotate()' method and animate the state change.

In this tutorial we stored this ‘degrees’ argument's value in a variable named ‘angle’ and we assigned this variable as the value of the ‘degrees’ parameter. To assign a value of the ‘angle’ variable we used the jetpack compose API’s ‘animateFloatAsState()’ method. This method allows us to display an animation when the ‘degrees’ float value changes, which means the float value state change.

The animateFloatAsState() method has four arguments those are targetValue, animationSpec, visibilityThreshold and finishedListener. Here ‘targetValue’ parameter is the main one, by changing this value caused the animation. As an example, when we change the target value by a Button click event from 0F to 180F, it will rotate the specified object from 0 degrees to 180 degrees with an animation. In this example code snippet, we changed the ‘targetValue’ from 0 to 360 degrees and reverse it on toggling Button click.

The second argument of the ‘animateFloatAsState()’ method is ‘animationSpec’, this is another important parameter. With this parameter, we can set the animation duration in milliseconds and we also can set an easing algorithm such as FastOutSlowInEasing, LinearEasing, etc. The ‘finishedListener’ parameter allows us to determine when the animation finish and then we can do some action after the animation is finished.

The following code snippets are written in an android studio IDE, so copy the code into your android studio main activity file and run it on your emulator device to visualize the rotate animation. This code is also self-descriptive, we hope you can understand the logic without running it in your android studio IDE.
MainActivity.kt

package com.cfsuman.jetpackcompose

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            MainContent()
        }
    }


    @Composable
    fun MainContent(){
        val isEnabled = remember { mutableStateOf(true) }
        val isRotated = remember { mutableStateOf(false) }

        val angle: Float by animateFloatAsState(
            targetValue = if (isRotated.value) 360F else 0F,
            animationSpec = tween(
                durationMillis = 2000, // duration
                easing = FastOutSlowInEasing
            ),
            finishedListener = {
                // disable the button
                isEnabled.value = true
            }
        )

        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(Color(0xFFF0F8FF))
                .padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Button(
                onClick = {
                    isRotated.value = !isRotated.value
                    isEnabled.value = false
                },
                colors = ButtonDefaults.buttonColors(
                    Color(0xFF4B0082), Color(0xCCFFFFFF)
                ),
                enabled = isEnabled.value
            ) {
                Text(
                    text = "Rotate Icon",
                    modifier = Modifier.padding(12.dp)
                )
            }

            Icon(
                Icons.Filled.LocationOn,
                contentDescription = "Localized description",
                Modifier
                    .size(300.dp)
                    .rotate(angle),
                tint = Color(0xFF5218FA)
            )
        }
    }


    @Preview
    @Composable
    fun ComposablePreview(){
        //MainContent()
    }
}