Building delightful UIs with Compose

Building delightful UIs with Compose

Home » News » Building delightful UIs with Compose
Table of Contents

Posted by Rebecca Franks – Developer Relations Engineer

Androidify is a brand new pattern app we constructed utilizing the newest greatest practices for cellular apps. Beforehand, we coated all of the totally different options of the app, from Gemini integration and CameraX performance to adaptive layouts. On this put up, we dive into the Jetpack Compose utilization all through the app, constructing upon our base information of Compose so as to add pleasant and expressive touches alongside the way in which!

Materials 3 Expressive

Materials 3 Expressive is an growth of the Materials 3 design system. It’s a set of recent options, up to date parts, and design techniques for creating emotionally impactful UX.

It’s been launched as a part of the alpha model of the Materials 3 artifact (androidx.compose.material3:material3:1.4.0-alpha10) and incorporates a variety of recent parts you should utilize inside your apps to construct extra personalised and pleasant experiences. Be taught extra about Materials 3 Expressive’s part and theme updates for extra participating and user-friendly merchandise.

Material Expressive Component updates

Materials Expressive Part updates

Along with the brand new part updates, Materials 3 Expressive introduces a brand new movement physics system that is encompassed within the Materials theme.

In Androidify, we’ve utilized Materials 3 Expressive in just a few alternative ways throughout the app. For instance, we’ve explicitly opted-in to the brand new MaterialExpressiveTheme and chosen MotionScheme.expressive() (that is the default when utilizing expressive) so as to add a little bit of playfulness to the app:

@Composable
enjoyable AndroidifyTheme(
   content material: @Composable () -> Unit,
) {
   val colorScheme = LightColorScheme


   MaterialExpressiveTheme(
       colorScheme = colorScheme,
       typography = Typography,
       shapes = shapes,
       motionScheme = MotionScheme.expressive(),
       content material = {
           SharedTransitionLayout {
               CompositionLocalProvider(LocalSharedTransitionScope supplies this) {
                   content material()
               }
           }
       },
   )
}

A few of the new componentry is used all through the app, together with the HorizontalFloatingToolbar for the Immediate sort choice:

moving example of expressive button shapes in slow motion

The app additionally makes use of MaterialShapes in numerous areas, that are a preset checklist of shapes that permit for straightforward morphing between one another. For instance, try the lovable cookie form for the digicam seize button:

Material Expressive Component updates

Digicam button with a MaterialShapes.Cookie9Sided form

Animations

Wherever potential, the app leverages the Materials 3 Expressive MotionScheme to acquire a themed movement token, making a constant movement feeling all through the app. For instance, the size animation on the digicam button press is powered by defaultSpatialSpec(), a specification used for animations that transfer one thing throughout a display screen (akin to x,y or rotation, scale animations):

val interactionSource = bear in mind { MutableInteractionSource() }
val animationSpec = MaterialTheme.motionScheme.defaultSpatialSpec<Float>()
Spacer(
   modifier
       .indication(interactionSource, ScaleIndicationNodeFactory(animationSpec))
       .clip(MaterialShapes.Cookie9Sided.toShape())
       .dimension(dimension)
       .drawWithCache {
           //.. and so on
       },
)

Camera button scale interaction

Digicam button scale interplay

Shared factor animations

The app makes use of shared factor transitions between totally different display screen states. Final 12 months, we showcased how one can create shared components in Jetpack Compose, and we’ve prolonged this within the Androidify pattern to create a enjoyable instance. It combines the brand new Materials 3 Expressive MaterialShapes, and performs a transition with a morphing form animation:

moving example of expressive button shapes in slow motion

To do that, we created a customized Modifier that takes within the goal and resting shapes for the sharedBounds transition:

@Composable
enjoyable Modifier.sharedBoundsRevealWithShapeMorph(
   sharedContentState: 
SharedTransitionScope.SharedContentState,
   sharedTransitionScope: SharedTransitionScope = 
LocalSharedTransitionScope.present,
   animatedVisibilityScope: AnimatedVisibilityScope = 
LocalNavAnimatedContentScope.present,
   boundsTransform: BoundsTransform = 
MaterialTheme.motionScheme.sharedElementTransitionSpec,
   resizeMode: SharedTransitionScope.ResizeMode = 
SharedTransitionScope.ResizeMode.RemeasureToBounds,
   restingShape: RoundedPolygon = RoundedPolygon.rectangle().normalized(),
   targetShape: RoundedPolygon = RoundedPolygon.circle().normalized(),
)

Then, we apply a customized OverlayClip to offer the morphing form, by tying into the AnimatedVisibilityScope offered by the LocalNavAnimatedContentScope:

val animatedProgress =
   animatedVisibilityScope.transition.animateFloat(targetValueByState = targetValueByState)


val morph = bear in mind {
   Morph(restingShape, targetShape)
}
val morphClip = MorphOverlayClip(morph, { animatedProgress.worth })


return this@sharedBoundsRevealWithShapeMorph
   .sharedBounds(
       sharedContentState = sharedContentState,
       animatedVisibilityScope = animatedVisibilityScope,
       boundsTransform = boundsTransform,
       resizeMode = resizeMode,
       clipInOverlayDuringTransition = morphClip,
       renderInOverlayDuringTransition = renderInOverlayDuringTransition,
   )

View the full code snippet for this Modifer on GitHub.

Autosize textual content

With the newest launch of Jetpack Compose 1.8, we added the flexibility to create textual content composables that mechanically alter the font dimension to suit the container’s obtainable dimension with the brand new autoSize parameter:

BasicText(textual content,
model = MaterialTheme.typography.titleLarge,
autoSize = TextAutoSize.StepBased(maxFontSize = 220.sp),
)

That is used entrance and middle for the “Customise your individual Android Bot” textual content:

Text reads Customize your own Android Bot with an inline moving image

“Customise your individual Android Bot” textual content with inline GIF

This textual content composable is fascinating as a result of it wanted to have the enjoyable dancing Android bot in the midst of the textual content. To do that, we use InlineContent, which permits us to append a composable in the midst of the textual content composable itself:

@Composable
personal enjoyable DancingBotHeadlineText(modifier: Modifier = Modifier) {
   Field(modifier = modifier) {
       val animatedBot = "animatedBot"
       val textual content = buildAnnotatedString {
           append(stringResource(R.string.customise))
           // Connect "animatedBot" annotation on the placeholder
           appendInlineContent(animatedBot)
           append(stringResource(R.string.android_bot))
       }
       var placeHolderSize by bear in mind {
           mutableStateOf(220.sp)
       }
       val inlineContent = mapOf(
           Pair(
               animatedBot,
               InlineTextContent(
                   Placeholder(
                       width = placeHolderSize,
                       top = placeHolderSize,
                       placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter,
                   ),
               ) {
                   DancingBot(
                       modifier = Modifier
                           .padding(prime = 32.dp)
                           .fillMaxSize(),
                   )
               },
           ),
       )
       BasicText(
           textual content,
           modifier = Modifier
               .align(Alignment.Middle)
               .padding(backside = 64.dp, begin = 16.dp, finish = 16.dp),
           model = MaterialTheme.typography.titleLarge,
           autoSize = TextAutoSize.StepBased(maxFontSize = 220.sp),
           maxLines = 6,
           onTextLayout = { end result ->
               placeHolderSize = end result.layoutInput.model.fontSize * 3.5f
           },
           inlineContent = inlineContent,
       )
   }
}

Composable visibility with onLayoutRectChanged

With Compose 1.8, a brand new modifier, Modifier.onLayoutRectChanged, was added. This modifier is a extra performant model of onGloballyPositioned, and contains options akin to debouncing and throttling to make it performant inside lazy layouts.

In Androidify, we’ve used this modifier for the colour splash animation. It determines the place the place the transition ought to begin from, as we connect it to the “Let’s Go” button:

var buttonBounds by bear in mind {
   mutableStateOf<RelativeLayoutBounds?>(null)
}
var showColorSplash by bear in mind {
   mutableStateOf(false)
}
Field(modifier = Modifier.fillMaxSize()) {
   PrimaryButton(
       buttonText = "Let's Go",
       modifier = Modifier
           .align(Alignment.BottomCenter)
           .onLayoutRectChanged(
               callback = { bounds ->
                   buttonBounds = bounds
               },
           ),
       onClick = {
           showColorSplash = true
       },
   )
}

We use these bounds as a sign of the place to start out the colour splash animation from.

moving image of a blue color splash transition between Androidify demo screens

Be taught extra pleasant particulars

From enjoyable marquee animations on the outcomes display screen, to animated gradient buttons for the AI-powered actions, to the trail drawing animation for the loading display screen, this app has many pleasant touches so that you can expertise and study from.

animated marquee example

animated gradient button for AI powered actions example

animated loading screen example

Take a look at the total codebase at github.com/android/androidify and study extra in regards to the newest in Compose from utilizing Materials 3 Expressive, the brand new modifiers, auto-sizing textual content and naturally a few pleasant interactions!

Discover this announcement and all Google I/O 2025 updates on io.google beginning Might 22.

Supply hyperlink

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 
share this article.

Enjoying my articles?

Sign up to get new content delivered straight to your inbox.

Please enable JavaScript in your browser to complete this form.
Name