Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
Android UI Development with Jetpack Compose

You're reading from   Android UI Development with Jetpack Compose Bring declarative and native UI to life quickly and easily on Android using Jetpack Compose and Kotlin

Arrow left icon
Product type Paperback
Published in Nov 2023
Publisher Packt
ISBN-13 9781837634255
Length 278 pages
Edition 2nd Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Thomas Künneth Thomas Künneth
Author Profile Icon Thomas Künneth
Thomas Künneth
Arrow right icon
View More author details
Toc

Table of Contents (18) Chapters Close

Preface 1. Part 1: Fundamentals of Jetpack Compose
2. Chapter 1: Building Your First Compose App FREE CHAPTER 3. Chapter 2: Understanding the Declarative Paradigm 4. Chapter 3: Exploring the Key Principles of Compose 5. Part 2: Building User Interfaces
6. Chapter 4: Laying Out UI Elements in Compose 7. Chapter 5: Managing State of Your Composable Functions 8. Chapter 6: Building a Real-World App 9. Chapter 7: Exploring App Architecture 10. Part 3: Advanced Topics
11. Chapter 8: Working with Animations 12. Chapter 9: Exploring Interoperability APIs 13. Chapter 10: Testing and Debugging Compose Apps 14. Chapter 11: Developing for Different Form Factors 15. Chapter 12: Bringing Your Compose UI to Different Platforms 16. Index 17. Other Books You May Enjoy

Using animation to visualize state changes

An app’s state is app data that may change over time. In a Compose app, state (for example, a color) is represented by State or MutableState instances. State changes trigger recompositions. The composable StateChangeDemo() shows a button and a box. Clicking the button toggles the color of the box between red and white by changing state:

@Composable
fun StateChangeDemo() {
  var toggled by remember {
    mutableStateOf(false)
  }
  val color = if (toggled)
    Color.White
  else
    Color.Red
  Column(
    modifier = Modifier
      .padding(16.dp),
    horizontalAlignment = Alignment.CenterHorizontally
  ) {
    Button(onClick = {
      toggled = !toggled
    }) {
      Text(
        stringResource(R.string.toggle)
      )
    }
    Box(
      modifier = Modifier
        .padding(top = 32.dp)
        .background(color = color)
        .size(128.dp)
    )
  }
}

In this example, color is a simple immutable variable. It is set each time toggled (a mutable Boolean state) changes. This happens inside onClick. As color is used with a modifier applied to Box() (background(color = color)), clicking the button changes the box color.

If you try the code, the change feels very sudden and abrupt. This is because white and red are not very similar colors. Using an animation will make the change much more pleasant. Let’s see how this works.

Animating single value changes

To animate a color, you can use the built-in animateColorAsState() composable. Just replace the val color = if (toggled) … assignment inside StateDemo() with the following code block. If you want to try it out, you can have a look at the SingleValueAnimationDemo() composable. It’s part of the AnimationDemo example app:

val color by animateColorAsState(
  targetValue = if (toggled)
    Color.White
  else
    Color.Red
)

animateColorAsState() returns a State<Color> instance. Whenever targetValue changes, the animation will run automatically. If the change occurs while the animation is in progress, the ongoing animation will adjust to match the new target value.

Tip

Using the by keyword, you can access the color state like you can with ordinary variables.

You can provide an optional listener to get notified when the animation is finished. The following line of code prints the color that matches the new state:

finishedListener = { color -> println(color)}

To customize your animation, you can pass an instance of AnimationSpec<Color> to animateColorAsState(). The default value is colorDefaultSpring, a private value in SingleValueAnimation.kt:

private val colorDefaultSpring = spring<Color>()

spring() is a top-level function in AnimationSpec.kt (which belongs to the android.compose.animation package). It receives a damping ratio, a stiffness, and a visibility threshold. The following line of code makes the color animation very soft:

animationSpec = spring(stiffness = Spring.StiffnessVeryLow)

spring() returns SpringSpec. This class implements the FiniteAnimationSpec interface, which in turn extends AnimationSpec. This interface defines the specification of an animation, which includes the data type to be animated and the animation configuration. In this case, it is a spring metaphor, though there are others. We will be returning to this interface in the Spicing up transitions through visual effects section. Next, we look at animating multiple value changes.

Animating multiple value changes

In this section, I will show you how to animate several values at once upon a state change. The setup is similar to StateDemo() and SingleValueAnimationDemo(): a Column() composable contains a Button() composable and a Box() composable. However, this time, the content of the box is Text(). The button toggles a state, which starts the animation. The following version of MultipleValuesAnimationDemo() does not yet contain an animation. It will be inserted below the comment reading FIXME: animation setup missing:

@Composable
fun MultipleValuesAnimationDemo() {
  var toggled by remember {
    mutableStateOf(false)
  }
  // FIXME: animation setup missing
  Column(
    modifier = Modifier
      .padding(16.dp),
    horizontalAlignment = Alignment.CenterHorizontally
  ) {
    Button(onClick = {
      toggled = !toggled
    }) {
      Text(
        stringResource(R.string.toggle)
      )
    }
    Box(
      contentAlignment = Alignment.Center,
      modifier = Modifier
        .padding(top = 32.dp)
        .border(
          width = borderWidth,
          color = Color.Black
        )
        .size(128.dp)
    ) {
      Text(
        text = stringResource(id = R.string.app_name),
        modifier = Modifier.rotate(degrees = degrees)
      )
    }
  }
}

The Box() shows a black border, whose width is controlled by borderWidth. To apply borders to your composable functions, just add the border() modifier. The child of the box, Text(), is rotated. You can achieve this with the rotate() modifier. The degrees variable holds the angle. degrees and borderWidth will change during the animation. Here’s how this is done:

val transition = updateTransition(
  targetState = toggled,
  label = "toggledTransition"
)
val borderWidth by transition.animateDp(
  label = "borderWidthTransition"
) { state ->
  if (state)
    10.dp
  else
    1.dp
}
val degrees by transition.animateFloat(
  label = "degreesTransition"
) { state ->
  if (state) -90F
  else
    0F
}

The updateTransition() composable function configures and returns a Transition. When targetState changes, the transition will run all of its child animations toward their target values. The label parameter is used to differentiate different transitions in Android Studio. While it is optional, please consider setting it because the transition can then be better inspected in the Animation Preview. Please refer to the Exercise section at the end of this chapter for more information about previewing animations.

Child animations are added using animate…() functions. They are not part of a Transition instance but are extension functions. animateDp() adds an animation based on density-independent pixels.

In my example, it controls the border width. animateFloat() creates a Float animation. This function is ideal for changing the rotation of Text(), which is a Float value. There are more animate…() functions, which operate on other data types. For example, animateInt() works with Int values. animateOffset() animates an Offset instance. You can find them in the Transition.kt file, which belongs to the androidx.compose.animation.core package.

Transition instances provide several properties reflecting the status of a transition. For example, isRunning indicates whether any animation in the transition is currently running. segment contains the initial state and the target state of the ongoing transition. The current state of the transition is available through currentState. This will be the initial state until the transition is finished. Then, currentState is set to the target state.

As you have seen, it is very easy to use state changes to trigger animations. So far, these animations have modified the visual appearance of one or more composable functions. In the next section, I will show you how to apply animations while showing or hiding UI elements.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Modal Close icon
Modal Close icon