Creating custom Android UIs just got a whole lot simpler


We have seen contextual mode in many apps like Whatsapp, Gmail, Contacts etc while selecting chats, emails, or contacts using a long press. It provides a temporary UI that displays a menu of contextual actions.
In a traditional view system, ActionMode is the preferred technique to display contextual actions. In this post, we will explore how the same can be achieved using Jetpack Compose.
Well, if you are interested in directly jumping to code, here is the link to the activity file that contains the code for achieving the above result.
This post assumes that you already know about Jetpack Compose and the Material Top App Bar implementation. Refer to the following links for a recap:
We will start by creating a Scaffold with a top app bar and a button that toggles the contextual mode. On button click, app bar colors are toggled and share action is added to indicate the contextual mode.
With this we have the following result:
Let us also change the status bar color when contextual mode is toggled. For this we will use Accompanist’s System UI Controller:
Add the dependency:
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
Change the status bar color:
With this change, we get the following result:
Now we have a fully functional contextual mode for our toolbar. But it would be nice to make this transition of colors smooth using some animations. Let us try to do that next.
Let us use updateTransition
API to animate the color changes. Replace the color variables with the following initialisation:
This leads to the following result:
With this, the color change is now animated but it looks like there are two different transitions. This is because the status bar color change is done in a side effect. To fix this, we need to gain full control of the status bar space. Once we have that control, status bar color change can be performed in the same manner as that of background and content color.
We will use the combination of setDecorFitsSystemWindows
API and the Accompanist’s Insets to get control of the space behind status bar.
Add the dependency:
implementation "com.google.accompanist:accompanist-insets:$accompanist_version"
Get edge to edge control of the device space by adding following code in onCreate
before calling setContent
:
WindowCompat.setDecorFitsSystemWindows(window, false)
Wrap the compose hierarchy with ProvideWindowInsets
. This enables us to use various modifiers provided by the insets library. Read more here.
setContent {
ProvideWindowInsets {
.
.
.
}
}
Update the side effect to set transparent status bar color. This is important to make sure that content behind the status bar (we will draw it in a moment) is visible:
SideEffect {
systemUiController.setStatusBarColor(
color = Color.Transparent,
darkIcons = !isContextual
)
}
Add following modifier to the TopAppBar invocation:
TopAppBar(
.
.
. modifier = Modifier.background(statusBarColor).statusBarsPadding()
)
This adds a padding equivalent to status bar height above top app bar and applies desired color to it.
With these changes, we get the following result:
Now the change happens in a single transition, but now we have unwanted top elevation in top app bar visible in normal mode. Jetpack Compose currently does not provide any way to specify the elevation on a specific edge. Let us use a workaround to fix this.
Let us use the workaround suggested by Philip Dukhov to solve this issue.
Create a custom modifier that applies only bottom elevation:
Apply this modifier to the Top App Bar composable:
TopAppBar(
.
.
. modifier = Modifier.background(statusBarColor).statusBarsPadding().bottomElevation()
)
With these changes, we got our final desired result as follow:
With this in place, we now have the fully functional contextual mode of top app bar implemented. You can also try to apply custom animation spec while calling animateColor
if you want some custom color change animation
- Build a custom top app bar component that takes
isContextual
boolean parameter. This will enable us to use it across all screens of the app - Support dark theme by using semantic colors from theme instead of hardcoded colors