{"id":277926,"date":"2025-06-06T15:16:02","date_gmt":"2025-06-06T15:16:02","guid":{"rendered":"https:\/\/michigandigitalnews.com\/index.php\/2025\/06\/06\/building-delightful-uis-with-compose\/"},"modified":"2025-06-25T17:08:10","modified_gmt":"2025-06-25T17:08:10","slug":"building-delightful-uis-with-compose","status":"publish","type":"post","link":"https:\/\/michigandigitalnews.com\/index.php\/2025\/06\/06\/building-delightful-uis-with-compose\/","title":{"rendered":"Building delightful UIs with Compose"},"content":{"rendered":"<p> [ad_1]<br \/>\n<\/p>\n<div>\n<meta name=\"twitter:image\" content=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEiuRcXkPQvPqpnXVGz8yrQY5qvmlvk6xlX0hFMTEFslcCNu27BKEcDLzvbW1nYOvRZmVd7aQ0mUpCSRRNw5OjwFjxDW-0gkkaLPb_aVav22fix6islazqbC2xexAiM1sxjF_nRgjlR5ffcXFIlW3673h9wBml8b0CdSZPqGezIRG-xxROyh2-BRY6ZPPko\/s1600\/blog_banner_androidify_dev_2.png\"\/><br \/>\n<img decoding=\"async\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEiuRcXkPQvPqpnXVGz8yrQY5qvmlvk6xlX0hFMTEFslcCNu27BKEcDLzvbW1nYOvRZmVd7aQ0mUpCSRRNw5OjwFjxDW-0gkkaLPb_aVav22fix6islazqbC2xexAiM1sxjF_nRgjlR5ffcXFIlW3673h9wBml8b0CdSZPqGezIRG-xxROyh2-BRY6ZPPko\/s1600\/blog_banner_androidify_dev_2.png\" style=\"display:none\"\/><\/p>\n<p><em>Posted by Rebecca Franks &#8211; Developer Relations Engineer<\/em><\/p>\n<p>Androidify is a new <a href=\"http:\/\/github.com\/android\/androidify\" target=\"_blank\" rel=\"noopener\">sample app<\/a> we built using the latest best practices for mobile apps. Previously, we covered <a href=\"https:\/\/android-developers.googleblog.com\/2025\/05\/androidify-building-ai-driven-experiences-jetpack-compose-gemini-camerax.html\" target=\"_blank\" rel=\"noopener\">all the different features of the app<\/a>, from Gemini integration and CameraX functionality to adaptive layouts. In this post, we dive into the Jetpack Compose usage throughout the app, building upon our base knowledge of Compose to add delightful and expressive touches along the way!<\/p>\n<p><a href=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEglSlE_fOzV5wgjf9ATKctAilcuw_2ANuKyWA0aWVE4FMPv_YkI-l0eUoiMzBjBFzOKXTd2qKS1U3M4HC25C8lIcY7ahbbGxdmacOSqqv7qpaDnZELUh9WfQleKrO3OAnkk84HlsI0EvhUlSMNICfk1AsZ1XvkKFAraQmoeOSr1F3dgSzGWQoBGNW6D0Iw\/s1600\/android-developers-androidify-compose.png\" imageanchor=\"1\"><img decoding=\"async\" border=\"0\" data-original-width=\"100%\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEglSlE_fOzV5wgjf9ATKctAilcuw_2ANuKyWA0aWVE4FMPv_YkI-l0eUoiMzBjBFzOKXTd2qKS1U3M4HC25C8lIcY7ahbbGxdmacOSqqv7qpaDnZELUh9WfQleKrO3OAnkk84HlsI0EvhUlSMNICfk1AsZ1XvkKFAraQmoeOSr1F3dgSzGWQoBGNW6D0Iw\/s1600\/android-developers-androidify-compose.png\" style=\"100%\"\/><\/a><\/p>\n<h2><span style=\"font-size: x-large;\">Material 3 Expressive<\/span><\/h2>\n<p>Material 3 Expressive is an expansion of the Material 3 design system. It\u2019s a set of new features, updated components, and design tactics for creating emotionally impactful UX.<\/p>\n<p><iframe title=\"Introducing: Material 3 Expressive\" width=\"1200\" height=\"675\" src=\"https:\/\/www.youtube.com\/embed\/n17dnMChX14?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>It\u2019s been released as part of the alpha version of the Material 3 artifact (<span style=\"color: #0d904f ; font-family: courier ;\">androidx.compose.material3:material3:1.4.0-alpha10<\/span>) and contains a wide range of new components you can use within your apps to build more personalized and delightful experiences. Learn more about <a href=\"https:\/\/m3.material.io\/blog\/building-with-m3-expressive?utm_source=blog&amp;utm_medium=motion&amp;utm_campaign=IO25\" target=\"_blank\" rel=\"noopener\">Material 3 Expressive&#8217;s component and theme updates for more engaging and user-friendly products<\/a>.<\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"Material Expressive Component updates\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEiC0jrz8_9qrnxHbn2oEvBsfmNoC8LJQL7bUUVoYzDzhE38IVt5IUtSNSy_FNyzr4RPrEcxB80TlgRHZnUgKnklYoG1v9mD51LXkPJsKHAXAZmAUzqS1YTPneiOjS-8PF0hFPOar3NEhli1fcw_zFFNqCxA3OU191FLv1JxCJ43PHC7sGao7fKpVya9xU0\/s1600\/material-expressive-component-update-androidify-google-io.png\" width=\"100%\"\/><\/div>\n<p><imgcaption><center><em>Material Expressive Component updates<\/em><\/center><\/imgcaption><\/image><\/p>\n<p>In addition to the new component updates, <a href=\"https:\/\/m3.material.io\/blog\/m3-expressive-motion-theming\" target=\"_blank\" rel=\"noopener\">Material 3 Expressive introduces a new motion physics system<\/a> that&#8217;s encompassed in the Material theme.<\/p>\n<p>In Androidify, we\u2019ve utilized Material 3 Expressive in a few different ways across the app. For example, we\u2019ve explicitly opted-in to the new <span style=\"color: #0d904f ; font-family: courier ;\">MaterialExpressiveTheme<\/span> and chosen <span style=\"color: #0d904f ; font-family: courier ;\">MotionScheme.expressive()<\/span> (this is the default when using expressive) to add a bit of playfulness to the app:<\/p>\n<p><!-- Kotlin --><\/p>\n<div style=\"background: #f8f8f8; overflow:auto;width:auto;border:0;\">\n<pre style=\"margin: 0; line-height: 125%\">@Composable\n<span style=\"color: #008000; font-weight: bold\">fun<\/span> <span style=\"color: #0000FF\">AndroidifyTheme<\/span>(\n   content: @Composable () -&gt; Unit,\n) {\n   <span style=\"color: #008000; font-weight: bold\">val<\/span> colorScheme = LightColorScheme\n\n\n   MaterialExpressiveTheme(\n       colorScheme = colorScheme,\n       typography = Typography,\n       shapes = shapes,\n       motionScheme = MotionScheme.expressive(),\n       content = {\n           SharedTransitionLayout {\n               CompositionLocalProvider(LocalSharedTransitionScope provides <span style=\"color: #008000; font-weight: bold\">this<\/span>) {\n                   content()\n               }\n           }\n       },\n   )\n}\n<\/pre>\n<\/div>\n<p>Some of the new componentry is used throughout the app, including the <span style=\"font-family: courier ;\"><a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/material3\/package-summary?hl=en#HorizontalFloatingToolbar%28kotlin.Boolean,androidx.compose.ui.Modifier,androidx.compose.material3.FloatingToolbarColors,androidx.compose.foundation.layout.PaddingValues,androidx.compose.material3.FloatingToolbarScrollBehavior,androidx.compose.ui.graphics.Shape,kotlin.Function1,kotlin.Function1,androidx.compose.ui.unit.Dp,androidx.compose.ui.unit.Dp,kotlin.Function1%29\" target=\"_blank\" rel=\"noopener\">HorizontalFloatingToolbar<\/a><\/span> for the Prompt type selection:<\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"moving example of expressive button shapes in slow motion\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEiU7_OHCwUDzW5FkzYAL6N-_nURWzxkLfCvWLBVsYCyPe0Hz9R4yLShuB8gIn8wFEw8FFlaPONKRDyJ_MBFoem53QnApdkb50TXeKB7FshcDaKqBFpZdeZJKfAO0j2NA9AzXtniI40AWHk2-_C6Jh7Shnqku9t2h2IDKcbXdZlP7K2wYcPJaYlfECNTIis\/s1600\/expressive-button-shapes-slow-mo.gif\" width=\"40%\"\/><\/div>\n<p><\/image><\/p>\n<p>The app also uses <span style=\"font-family: courier ;\"><a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/material3\/MaterialShapes\" target=\"_blank\" rel=\"noopener\">MaterialShapes<\/a><\/span> in various locations, which are a preset list of shapes that allow for easy morphing between each other. For example, check out the cute cookie shape for the camera capture button:<\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"Material Expressive Component updates\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEiFKuWr-zx8wUpeSuXNWFRNJ754V7JMUUuCcWphB6H7FnXIpYZ-Rpob2miMmjDNxREmm4cA0AMUekJBPTgfqJh4dsu4IFNbWFEEOWrJwz4BtX5sBvVZ5U3apYKSpJLkZSG6qEG7HuFVifYdm991MwgYQEDREFfddoZ_Zs7PjIeY8KMWugC8uK5daEksuDs\/s1600\/camera-button-materialshapes-androidify-google-io.png\" width=\"40%\"\/><\/div>\n<p><imgcaption><center><em>Camera button with a <span style=\"color: #0d904f ; font-family: courier ;\">MaterialShapes.Cookie9Sided<\/span> shape<\/em><\/center><\/imgcaption><\/image><\/p>\n<h2><span style=\"font-size: x-large;\">Animations<\/span><\/h2>\n<p>Wherever possible, the app leverages the Material 3 Expressive <span style=\"color: #0d904f ; font-family: courier ;\">MotionScheme<\/span> to obtain a themed motion token, creating a consistent motion feeling throughout the app. For example, the scale animation on the camera button press is powered by <span style=\"color: #0d904f ; font-family: courier ;\">defaultSpatialSpec()<\/span>, a specification used for animations that move something across a screen (such as x,y or rotation, scale animations):<\/p>\n<p><!-- Kotlin --><\/p>\n<div style=\"background: #f8f8f8; overflow:auto;width:auto;border:0;\">\n<pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #008000; font-weight: bold\">val<\/span> interactionSource = remember { MutableInteractionSource() }\n<span style=\"color: #008000; font-weight: bold\">val<\/span> animationSpec = MaterialTheme.motionScheme.defaultSpatialSpec<float>()\nSpacer(\n   modifier\n       .indication(interactionSource, ScaleIndicationNodeFactory(animationSpec))\n       .clip(MaterialShapes.Cookie9Sided.toShape())\n       .size(size)\n       .drawWithCache {\n           <span style=\"color: #408080; font-style: italic\">\/\/.. etc<\/span>\n       },\n)\n<\/float><\/pre>\n<\/div>\n<p><\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"Camera button scale interaction\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEhlK4tJb8rYd9AQL28jHsPX4HlFvVUPbKchXxNflZPcFewj3M4nOU8-5UmoR8eJLIWDc7x6DS0zqWzKxCjopy95f7iA01lSRA1FNHzyT3yN8PFU__c2eVkIrtCzsUYqSpG3dn33iwBVRIVbsE_spM_ivS_7VIpnAfBGeklZxQJ9kykgwSQGHaGSfCvEIYg\/s1600\/camera-button-scale-interaction-androidify-google-io.gif\" width=\"75%\"\/><\/div>\n<p><imgcaption><center><em>Camera button scale interaction<\/em><\/center><\/imgcaption><\/image><\/p>\n<h2><span style=\"font-size: x-large;\">Shared element animations<\/span><\/h2>\n<p>The app uses shared element transitions between different screen states. Last year, we showcased how you can <a href=\"https:\/\/developer.android.com\/develop\/ui\/compose\/animation\/shared-elements\" target=\"_blank\" rel=\"noopener\">create shared elements in Jetpack Compose<\/a>, and we\u2019ve extended this in the Androidify sample to create a fun example. It combines the new Material 3 Expressive <span style=\"color: #0d904f ; font-family: courier ;\">MaterialShapes<\/span>, and performs a transition with a morphing shape animation:<\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"moving example of expressive button shapes in slow motion\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEhWVTQUwtQo13XnUS8EByVxH7Ou3rUWqyvY2BpsJh-Ub6ZA4_S3Z3yPZORg9TAUZvHNsaNjhUsAq3bcGZKDStCaeUCzcqxBDu4zwcalJ9ZSFKcyJyqocu6YVvv3yMhNuz-getfnHY5uVD6royUWUm58nBPCwQmA30uMUqopjL_MhRWYn10yeJgsf-yfiV0\/s1600\/morph-shared-element-androidify-google-io.gif\" width=\"40%\"\/><\/div>\n<p><\/image><\/p>\n<p>To do this, we created a custom <span style=\"color: #0d904f ; font-family: courier ;\">Modifier<\/span> that takes in the target and resting shapes for the <span style=\"color: #0d904f ; font-family: courier ;\">sharedBounds<\/span> transition:<\/p>\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #f8f8f8; overflow:auto;width:auto;border:0;\">\n<pre style=\"margin: 0; line-height: 125%\">@Composable\n<span style=\"color: #008000; font-weight: bold\">fun<\/span> Modifier.sharedBoundsRevealWithShapeMorph(\n   sharedContentState: \nSharedTransitionScope.SharedContentState,\n   sharedTransitionScope: SharedTransitionScope = \nLocalSharedTransitionScope.current,\n   animatedVisibilityScope: AnimatedVisibilityScope = \nLocalNavAnimatedContentScope.current,\n   boundsTransform: BoundsTransform = \nMaterialTheme.motionScheme.sharedElementTransitionSpec,\n   resizeMode: SharedTransitionScope.ResizeMode = \nSharedTransitionScope.ResizeMode.RemeasureToBounds,\n   <b>restingShape: RoundedPolygon = RoundedPolygon.rectangle().normalized(),\n   targetShape: RoundedPolygon = RoundedPolygon.circle().normalized(),<\/b>\n)\n<\/pre>\n<\/div>\n<p>Then, we apply a custom <span style=\"color: #0d904f ; font-family: courier ;\">OverlayClip<\/span> to provide the morphing shape, by tying into the <span style=\"color: #0d904f ; font-family: courier ;\">AnimatedVisibilityScope<\/span> provided by the <span style=\"color: #0d904f ; font-family: courier ;\">LocalNavAnimatedContentScope<\/span>:<\/p>\n<p><!-- Kotlin --><\/p>\n<div style=\"background: #f8f8f8; overflow:auto;width:auto;border:0;\">\n<pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #008000; font-weight: bold\">val<\/span> animatedProgress =\n   animatedVisibilityScope.transition.animateFloat(targetValueByState = targetValueByState)\n\n\n<span style=\"color: #008000; font-weight: bold\">val<\/span> morph = remember {\n   Morph(restingShape, targetShape)\n}\n<span style=\"color: #008000; font-weight: bold\">val<\/span> morphClip = MorphOverlayClip(morph, { animatedProgress.value })\n\n\n<span style=\"color: #008000; font-weight: bold\">return<\/span> <span style=\"color: #008000; font-weight: bold\">this<\/span>@sharedBoundsRevealWithShapeMorph\n   .sharedBounds(\n       sharedContentState = sharedContentState,\n       animatedVisibilityScope = animatedVisibilityScope,\n       boundsTransform = boundsTransform,\n       resizeMode = resizeMode,\n       clipInOverlayDuringTransition = morphClip,\n       renderInOverlayDuringTransition = renderInOverlayDuringTransition,\n   )\n<\/pre>\n<\/div>\n<p>View the <a href=\"https:\/\/github.com\/android\/androidify\/blob\/770f690d74bbaccaa5e8cd0fa88c4bdcf244b87c\/core\/theme\/src\/main\/java\/com\/android\/developers\/androidify\/theme\/SharedElementsConfig.kt#L185\" target=\"_blank\" rel=\"noopener\">full code snippet for this <span style=\"font-family: courier ;\">Modifer<\/span> on GitHub<\/a>.<\/p>\n<h2><span style=\"font-size: x-large;\">Autosize text<\/span><\/h2>\n<p>With the latest release of <a href=\"https:\/\/android-developers.googleblog.com\/2025\/04\/whats-new-in-jetpack-compose-april-25.html\" target=\"_blank\" rel=\"noopener\">Jetpack Compose 1.8<\/a>, we added the ability to create text composables that automatically adjust the font size to fit the container\u2019s available size with the new autoSize parameter:<\/p>\n<p><!-- Kotlin --><\/p>\n<div style=\"background: #f8f8f8; overflow:auto;width:auto;border:0;\">\n<pre style=\"margin: 0; line-height: 125%\">BasicText(text,\nstyle = MaterialTheme.typography.titleLarge,\nautoSize = TextAutoSize.StepBased(maxFontSize = <span style=\"color: #666666\">220.<\/span>sp),\n)\n<\/pre>\n<\/div>\n<p>This is used front and center for the \u201cCustomize your own Android Bot\u201d text:<\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"Text reads Customize your own Android Bot with an inline moving image\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEjOUbdg-XFBpDsB0tNCcWnElfPMk6fDpxtwS0FpiifNqe63qopzYI0jjX19Obv4milyZOlyMsTwBOyrr5ei24DHeJU9mn7GkPGgKSatGIe2Xe2_HLUJdCSXKOedEgQS14QqrCmzx2Haos-aRCgqx3sVZz6CuZiobH2yI5O9S5Y006A7eNihPpwM8uvsWLc\/s1600\/customize-your-own-android-bot-androidify-google-io.png\" width=\"40%\"\/><\/div>\n<p><imgcaption><center><em><i>\u201cCustomize your own Android Bot\u201d<\/i> text with inline GIF<\/em><\/center><\/imgcaption><\/image><\/p>\n<p>This text composable is interesting because it needed to have the fun dancing Android bot in the middle of the text. To do this, we use <span style=\"color: #0d904f ; font-family: courier ;\">InlineContent<\/span>, which allows us to append a composable in the middle of the text composable itself:<\/p>\n<p><!-- Kotlin --><\/p>\n<div style=\"background: #f8f8f8; overflow:auto;width:auto;border:0;\">\n<pre style=\"margin: 0; line-height: 125%\">@Composable\n<span style=\"color: #008000; font-weight: bold\">private<\/span> <span style=\"color: #008000; font-weight: bold\">fun<\/span> <span style=\"color: #0000FF\">DancingBotHeadlineText<\/span>(modifier: Modifier = Modifier) {\n   Box(modifier = modifier) {\n       <span style=\"color: #008000; font-weight: bold\">val<\/span> animatedBot = <span style=\"color: #BA2121\">\"animatedBot\"<\/span>\n       <span style=\"color: #008000; font-weight: bold\">val<\/span> text = buildAnnotatedString {\n           append(stringResource(R.string.customize))\n           <span style=\"color: #408080; font-style: italic\">\/\/ Attach \"animatedBot\" annotation on the placeholder<\/span>\n           appendInlineContent(animatedBot)\n           append(stringResource(R.string.android_bot))\n       }\n       <span style=\"color: #008000; font-weight: bold\">var<\/span> placeHolderSize by remember {\n           mutableStateOf(<span style=\"color: #666666\">220.<\/span>sp)\n       }\n       <span style=\"color: #008000; font-weight: bold\">val<\/span> inlineContent = mapOf(\n           Pair(\n               animatedBot,\n               InlineTextContent(\n                   Placeholder(\n                       width = placeHolderSize,\n                       height = placeHolderSize,\n                       placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter,\n                   ),\n               ) {\n                   DancingBot(\n                       modifier = Modifier\n                           .padding(top = <span style=\"color: #666666\">32.d<\/span>p)\n                           .fillMaxSize(),\n                   )\n               },\n           ),\n       )\n       BasicText(\n           text,\n           modifier = Modifier\n               .align(Alignment.Center)\n               .padding(bottom = <span style=\"color: #666666\">64.d<\/span>p, start = <span style=\"color: #666666\">16.d<\/span>p, end = <span style=\"color: #666666\">16.d<\/span>p),\n           style = MaterialTheme.typography.titleLarge,\n           autoSize = TextAutoSize.StepBased(maxFontSize = <span style=\"color: #666666\">220.<\/span>sp),\n           maxLines = <span style=\"color: #666666\">6<\/span>,\n           onTextLayout = { result -&gt;\n               placeHolderSize = result.layoutInput.style.fontSize * <span style=\"color: #666666\">3.5f<\/span>\n           },\n           inlineContent = inlineContent,\n       )\n   }\n}\n<\/pre>\n<\/div>\n<h2><span style=\"font-size: x-large;\">Composable visibility with <span style=\"color: #0d904f ; font-family: courier ;\">onLayoutRectChanged<\/span><\/span><\/h2>\n<p>With <a href=\"https:\/\/android-developers.googleblog.com\/2025\/04\/whats-new-in-jetpack-compose-april-25.html\" target=\"_blank\" rel=\"noopener\">Compose 1.8<\/a>, a new modifier, <span style=\"font-family: courier ;\"><a href=\"https:\/\/developer.android.com\/reference\/kotlin\/androidx\/compose\/ui\/Modifier?hl=en#%28androidx.compose.ui.Modifier%29.onLayoutRectChanged%28kotlin.Long,kotlin.Long,kotlin.Function1%29\" target=\"_blank\" rel=\"noopener\">Modifier.onLayoutRectChanged<\/a><\/span>, was added. This modifier is a more performant version of <span style=\"color: #0d904f ; font-family: courier ;\">onGloballyPositioned<\/span>, and includes features such as debouncing and throttling to make it performant inside lazy layouts.<\/p>\n<p>In Androidify, we\u2019ve used this modifier for the color splash animation. It determines the position  where the transition should start from, as we attach it to the \u201cLet\u2019s Go\u201d button:<\/p>\n<p><!-- Kotlin --><\/p>\n<div style=\"background: #f8f8f8; overflow:auto;width:auto;border:0;\">\n<pre style=\"margin: 0; line-height: 125%\"><span style=\"color: #008000; font-weight: bold\">var<\/span> buttonBounds by remember {\n   mutableStateOf<relativelayoutbounds>(<span style=\"color: #008000; font-weight: bold\">null<\/span>)\n}\n<span style=\"color: #008000; font-weight: bold\">var<\/span> showColorSplash by remember {\n   mutableStateOf(<span style=\"color: #008000; font-weight: bold\">false<\/span>)\n}\nBox(modifier = Modifier.fillMaxSize()) {\n   PrimaryButton(\n       buttonText = <span style=\"color: #BA2121\">\"Let's Go\"<\/span>,\n       modifier = Modifier\n           .align(Alignment.BottomCenter)\n           .onLayoutRectChanged(\n               callback = { bounds -&gt;\n                   buttonBounds = bounds\n               },\n           ),\n       onClick = {\n           showColorSplash = <span style=\"color: #008000; font-weight: bold\">true<\/span>\n       },\n   )\n}\n<\/relativelayoutbounds><\/pre>\n<\/div>\n<p>We use these bounds as an indication of where to start the color splash animation from.<\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"moving image of a blue color splash transition between Androidify demo screens\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEi_5rJvIIo6ioe_Jo5dkrOD7OuZdo4xJHbmnIqr-kVB6pViOimjtcqZUnxZD2trpDTvoySZWcxx4RjqQEPYhlm1vfr8M_NY7Y9rl7ea7XOUneHLGlIxHWQBjIGKaZZ78SddX1UoUYPoG88fNWcr9t09ShrI_KFZcg8nZaUF59enD7sbcL5x5gsLl-jsQiA\/s1600\/color-splash-androidify-google-io.gif\" width=\"40%\"\/><\/div>\n<p><\/image><\/p>\n<h2><span style=\"font-size: x-large;\">Learn more delightful details<\/span><\/h2>\n<p>From fun marquee animations on the results screen, to animated gradient buttons for the AI-powered actions, to the path drawing animation for the loading screen, this app has many delightful touches for you to experience and learn from.<\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"animated marquee example\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEivftZEN51EFmOej_1nfp-7zrGWUxbJx7nEx5GgVAtjlK9t4f49VmKE0RSOWP6zhkhMTaj7fnYq5SH5su_5hzxCGoX9wXiyVEROOmPAwIev5dk6O8Uw_cIcPmqERVEA48vD4jcN3pSJvzHhLEchXU5h5locO8qOQFGO-eErP-oXSWU017BaF7Q3Auvjlh4\/s1600\/end-screen-animation-androidify-google-io.gif\" width=\"40%\"\/><\/div>\n<p><\/image><\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"animated gradient button for AI powered actions example\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEj1RYgxeWS2GZA6Lk9JK0E74VBJBhHeySxP12qXhYwGLl42-o1pdKBCdQY_QUq5z0yW_eHnaRFqT5TiAqyjGJVZ2TqH8WO-QqqJYWhPWjrUJfHveBowy_h5ltkZ9xa54QuAS0e2pANkENbkg0qb5CaaU8Zp5u-ebYgbC-i6HJ_ACzG_sdS6q1aH89cgAZk\/s1600\/animated-gradient-button-androidify-google-io.gif\" width=\"40%\"\/><\/div>\n<p><\/image><\/p>\n<p><image><\/p>\n<div style=\"text-align: center;\"><img decoding=\"async\" alt=\"animated loading screen example\" border=\"0\" id=\"imgCaption\" src=\"https:\/\/blogger.googleusercontent.com\/img\/b\/R29vZ2xl\/AVvXsEh55cQzGPIITkAKUyTjNkhyphenhyphenXdP6MhVqLLJMKK6ecmFzxpCqbE2R0IdVghz4W3E6Lq4SWRHr9Z5Xm4kjPfwYpdosUoSZMk0OpEiEhyphenhyphenl0FSj9MSaCEzcgYdujoXVuPvFrjuYeY_dzUFQ9R_pZNrdQ-rV9EOa_IoaHFsvx3Dp5zNbVnBmCrXwgh1nCtRUU0qs\/s1600\/loading-screen-animation-androidify-google-io.gif\" width=\"40%\"\/><\/div>\n<p><\/image><\/p>\n<p>Check out the full codebase at <a href=\"http:\/\/github.com\/android\/androidify\" target=\"_blank\" rel=\"noopener\">github.com\/android\/androidify<\/a> and learn more about the latest in Compose from using Material 3 Expressive, the new modifiers, auto-sizing text and of course a couple of delightful interactions!<\/p>\n<p>Explore this announcement and all Google I\/O 2025 updates on <a href=\"https:\/\/io.google\/2025\/?utm_source=blogpost&amp;utm_medium=pr&amp;utm_campaign=event&amp;utm_content=\" target=\"_blank\" rel=\"noopener\">io.google<\/a> starting May 22.<\/p>\n<\/div>\n<p>[ad_2]<br \/>\n<br \/><a href=\"http:\/\/android-developers.googleblog.com\/2025\/05\/androidify-building-delightful-ui-with-compose.html\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>[ad_1] Posted by Rebecca Franks &#8211; Developer Relations Engineer Androidify is a new sample app we built using the latest best practices for mobile apps.<\/p>\n","protected":false},"author":1,"featured_media":277927,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[146],"tags":[],"_links":{"self":[{"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/posts\/277926"}],"collection":[{"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/comments?post=277926"}],"version-history":[{"count":0,"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/posts\/277926\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/media\/277927"}],"wp:attachment":[{"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/media?parent=277926"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/categories?post=277926"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/michigandigitalnews.com\/index.php\/wp-json\/wp\/v2\/tags?post=277926"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}