Material You in Jetpack Compose

Learn how to use the amazing features that come with the new Material Design 3 to create better-looking apps with a more personal feel. By Harun Wangereka.

5 (5) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Using Material Motion in Your App

Apps need to move from one screen to another or one component to another. While doing that, you can add some transitions to give this move a smooth and delightful feel.

Material Motion is built upon a set of transition patterns that help improve your app’s user experience by coupling all UI elements and consolidating their relationship.

Understanding Transition Patterns

The Material Design library supports four motion patterns:

  1. Container transform: Creates a visible connection between two elements. For instance, a card transitioning to a details screen. You can set up container transformation for views, fragments and activities.
  2. Fade through: Useful in transition between elements that don’t have a relationship with each other. You can apply it in bottom bar navigation.
  3. Fade: Useful for elements that enter and exit within a screen. Examples are dialogs, snackbars and menu dialogs.
  4. Shared axis: Used for elements that have a navigational relationship. Uses the x, y and z axes to create this relationship between elements.

It’s time you get your hands dirty by adding some of these patterns to your app by animating:

  • The transitions between screens.
  • Some of the components in your app, like dialog.
  • Showing an event description.

You’ll start by adding fade animations to your color picker AlertDialog.

Adding Fade Animations to ColorPicker AlertDialog

Navigate to presentation/composables/ColorPicker.kt, locate // TODO Add Alert Dialog with Fade Animations, and replace it with:

 // 1
  AnimatedVisibility(
    visible = showDialog,
    enter = fadeIn(),
    exit = fadeOut()
  ) {
    LocalFocusManager.current.clearFocus()
    // 2
    AlertDialog(
      onDismissRequest = { },
      title = { Text(text = "Select Color") },
      text = {
        // 3
        LazyColumn(
          content = {
            items(eventColors) { eventColor ->
              // 4
              Row(
                modifier = Modifier
                  .fillMaxWidth()
                  .padding(top = 8.dp, bottom = 8.dp)
                  .clickable(
                    indication = rememberRipple(bounded = true),
                    interactionSource = remember {
                      MutableInteractionSource()
                    }
                  ) {
                    // 5
                    showDialog = false
                    colorSelected = eventColor
                    eventsViewModel.userSelectedColor = colorSelected.color.colorToString()
                  }
              ) {
                Box(
                  modifier = Modifier
                    .size(25.dp)
                    .padding(5.dp)
                    .align(alignment = Alignment.CenterVertically)
                    .clip(shape = RoundedCornerShape(15.dp))
                    .background(eventColor.color)
                )

                Text(
                  text = eventColor.name,
                  modifier = Modifier
                    .fillMaxWidth()
                    .padding(5.dp)
                    .align(alignment = Alignment.CenterVertically),
                  fontSize = 12.sp
                )
              }
            }
          }
        )
      },
      confirmButton = { },
      dismissButton = { },
    )
  }

In addition, add these imports to resolve all your import errors:

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.material3.AlertDialog
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.ui.unit.sp
import com.app.composematerialyou.utils.colorToString
import com.app.composematerialyou.utils.eventColors

To sum up, here’s a code breakdown:

  1. Here, you use AnimatedVisibility to add your fade animations and set enter and exit, then control the visibility of the animations with the showDialog variable. For example, you set it to true when a user taps the color picker view.
  2. With the AlertDialog composable, you create a color picker with four colors, and set the title and other properties for your AlertDialog.
  3. This is a LazyColumn that displays a list of event colors.
  4. Then, you create a single Row for a single color.
  5. Lastly, when the user chooses their color, you reset your showDialog variable, and set this color to EventsViewModel.

Build and run your app. Tap Create, fill in the event details and tap the color picker view. As a result, you should see:

Color picker dialog

The app shows an AlertDialog with a list of event colors. Good job! Now you can pick a color for your event.

Next, you’ll animate the appearance of the calendar event description.

Adding Animation to Text

To begin with, navigate to presentation/composables/CalendarListItem.kt, locate // TODO Add Animated Visibility Animation and replace it with:

AnimatedVisibility(visible = showDescription) {
  Text(
    text = userEvent.description,
    color = Color.White
  )
  Spacer(
    modifier = Modifier
      .padding(top = 10.dp)
  )
}

Add any missing imports by pressing Option-Enter on Mac or Alt-Enter on Windows PC.

In the code above, you animate the visibility of the event description Text composable. You use the AnimatedVisibility() to animate the visibility of the contents inside.

Build and run your app. Add a new event and tap the arrow icon to toggle the visibility of the description.

Toggle event description showing and hiding details

Nice work! You’ve added fade and visibility animations. In the next section, you’ll learn how to add transitions from one destination to another.

Adding Transitions on Navigating Between Destinations

Your app doesn’t have animations while transitioning from the events screen to the input screen. You’ll add these transitions to give your app a rich user experience.

To do that, you’ll use Accompanist, which provides libraries that supplement Jetpack Compose features. For example, it has the Navigation-Animation library that adds support for navigation animations in Compose.

Using Accompanist in Your App

Navigate to your app’s build.gradle file, locate // TODO Add Accompanist Dependencies and replace it with:

// Accompanist
implementation "com.google.accompanist:accompanist-navigation-animation:0.24.1-alpha"

This code adds the Accompanist navigation animation dependency. After that, do a Gradle sync.

Next, you need to add the animations to your NavHost. To do that, navigate to presentation/navigation/NavHost.kt and replace all file contents, including imports, with:

import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandIn
import androidx.compose.animation.shrinkOut
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.navigation.NavHostController
import com.app.composematerialyou.presentation.screens.EventScreen
import com.app.composematerialyou.presentation.screens.HomeScreen
import com.app.composematerialyou.presentation.viewmodels.EventsViewModel
import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.composable

@ExperimentalAnimationApi
@ExperimentalComposeUiApi
@ExperimentalMaterial3Api
@Composable
fun AppNavigation(navController: NavHostController, eventsViewModel: EventsViewModel) {
  AnimatedNavHost(
    navController = navController,
    startDestination = Screens.HomeScreen.route,
    enterTransition = { expandIn(animationSpec = tween(800)) }, // 1
    exitTransition = { shrinkOut(animationSpec = tween(800)) } // 2
  ) {
    // TODO Add Home Screen Destination

    // TODO Add Event Input Screen Composable
  }
}

Here’s what the code does:

  1. It defines enterTransition for all destinations in this NavHost. If you don’t provide any at the destination level, AnimatedNavHost uses this one.
  2. Then, it defines exitTransition for all destinations in this NavHost.

You also have two TODOs for adding your two destinations in your app navigation. You’ll add them in a moment.

Adding Transitions to the Home Screen

You’ll start by adding the Home Screen destination by replacing // TODO Add Home Screen Destination with:

composable(Screens.HomeScreen.route,
  // 1
  enterTransition = {
    if (initialState.destination.route == Screens.EventInputScreen.route) slideIntoContainer(
      AnimatedContentScope.SlideDirection.Right,
      animationSpec = tween(600)
    )
    else null
  },
  // 2
  exitTransition = {
    if (targetState.destination.route == Screens.EventInputScreen.route) slideOutOfContainer(
      AnimatedContentScope.SlideDirection.Left,
      animationSpec = tween(600)
    )
    else null
  }
) {
  HomeScreen(navController, eventsViewModel)
}

Here’s a code breakdown:

  1. This is the destination’s specific composable enterTransition. This animation runs when you navigate to this destination. You’ve set the value to a horizontal slide-in animation.
  2. And this is the destination’s specific composable exitTransition. This animation runs when you leave this destination while navigating to other destinations. You’ve set the value to a horizontal slide-in animation.