Blog Infos
Author
Published
Topics
, , , ,
Published
Handle some optional changes to have an immersive edge-to-edge display experience in your android apps targeting Android 15

In this part, we going to discuss how to have a better UI/UX experience after making your app compatible with edge-to-edge display. So, If you have no clue on how to make your app compatible with edge-to-edge display then I would recommend you to first go through the Part-I of this article below:

App theme adjustment (Optional)

To have an immersive UI/UX experience in our app after adapting edge-to-edge display we need to do some theme adjustment. So, now when you see this app’s activity_main.xml layout (as shown in Fig 1) after edge-to-edge adaption in it — What are the theme adjustment thought comes into your mind? Well, for me there are two:

  • AppBarLayout should have a background consistent with status bar.
  • Status bar icon color should be in contrast with the window background.

Fig 1. Edge-to-edge layout

The reason for aligning AppBarLayout background with status bar instead of vice-versa is because of the earlier discussed changes of Android 15:

  • Status bar is transparent by default.
  • setStatusBarColor and R.attr#statusBarColor are deprecated and have no effect on Android 15.

So, one way to change the background of AppBarLayout in our activity_main.xml is to apply the background on its child views (Toolbar & TabLayout). However, It will not be ideal solution as there are additional changes related to Toolbar foreground & TabLayout tab foreground will be required as well. So, best is to apply it on app theme.

Initially I thought to add these changes in a single app theme (exist inside values/themes.xml) to make it compatible with all Android OS versions up-to API 21. However, some of the required theme attributes were not available up-to API 21 such as:

  • android:windowLightStatusBar which is required for indicating the system to have dark status bar icon when it’s true however it’s only available from API 23. Actually, status bar should automatically adapt the icon color based on Light/Dark theme but It seems to be working consistently only from API 29 onwards.
  • android:windowLightNavigationBar which is required for indicating the system to have dark navigation bar icon when it’s true however it’s only available from API 27. Similar to status bar, navigation bar should also automatically adapt the icon color based on Light/Dark theme but it’s also seems to be working consistently only from API 29 onwards.

So, I decided to kept the existing app theme from API 21 to API 26 and provided new theme from API 27 onwards. Here is the complete arrangement I did for light theme across two different theme files:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--Base application theme, dependent on API level-->
<style name="Theme.IHackerNews.DayNight.Base" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!--Status bar color-->
<item name="android:statusBarColor">@color/primary</item>
<!--Window background & foreground color-->
<item name="android:windowBackground">@color/window_light_bg</item>
<item name="android:textColorPrimary">@android:color/black</item>
<!--List Item separator color-->
<item name="listItemSeparatorColor">@color/lighter_gray</item>
<!--Toolbar background & foreground color-->
<item name="toolbarBackground">@color/primary</item>
<item name="toolbarForeground">@android:color/white</item>
</style>
<style name="Theme.IHackerNews" parent="Theme.IHackerNews.DayNight.Base">
<!--Primary brand color-->
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/primary</item>
<item name="colorOnPrimary">@android:color/white</item>
<!--Secondary brand color-->
<item name="colorSecondary">@color/secondary</item>
<item name="colorSecondaryVariant">@color/secondary_dark</item>
<item name="colorOnSecondary">@android:color/black</item>
<!--Toolbar default action menu icon tint color-->
<item name="colorControlNormal">?attr/toolbarForeground</item>
<!--Toolbar navigation & custom action menu icon tint color-->
<item name="navigationIconTint">?attr/toolbarForeground</item>
<!--Control highlight color-->
<item name="colorControlHighlight">@color/primary</item>
<!--Toolbar style-->
<item name="toolbarStyle">@style/ToolbarTheme</item>
<!--TabLayout style-->
<item name="tabStyle">@style/TabLayoutTheme</item>
</style>
<!--Base toolbar theme, dependent on API level-->
<style name="ToolbarTheme.Base" parent="Widget.MaterialComponents.Toolbar"/>
<style name="ToolbarTheme" parent="ToolbarTheme.Base">
<item name="android:background">?attr/toolbarBackground</item>
<item name="titleTextColor">?attr/toolbarForeground</item>
</style>
<!--Base tab layout theme, dependent on API level-->
<style name="TabLayoutTheme.Base" parent="Widget.MaterialComponents.TabLayout">
<item name="tabTextColor">@color/white_50</item>
<item name="tabIndicatorColor">?attr/colorOnPrimary</item>
<item name="tabSelectedTextColor">?attr/colorOnPrimary</item>
</style>
<style name="TabLayoutTheme" parent="TabLayoutTheme.Base">
<item name="android:background">?attr/toolbarBackground</item>
</style>
</resources>
view raw themes.xml hosted with ❤ by GitHub

App theme including base theme for API 21 to API 26

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.IHackerNews.DayNight.Base" parent="Theme.MaterialComponents.DayNight">
<!--Edge-to-Edge config-->
<item name="android:statusBarColor" >@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">true</item>
<!--Window background & foreground color-->
<item name="android:windowBackground">@color/window_light_bg</item>
<item name="android:textColorPrimary">@android:color/black</item>
<!--List Item separator color-->
<item name="listItemSeparatorColor">@color/lighter_gray</item>
<!--Toolbar background & foreground color-->
<item name="toolbarBackground">@color/window_light_bg</item>
<item name="toolbarForeground">@color/on_primary_light</item>
</style>
<style name="ToolbarTheme.Base" parent="Widget.MaterialComponents.Toolbar"/>
<style name="TabLayoutTheme.Base" parent="Widget.MaterialComponents.TabLayout"/>
</resources>

Base theme from API 27 onwards

This way not only AppBarLayout but status bar & navigation bar also become consistent with the theme.

Fig 2. App theme from API 21 to API 27

 

 

Fig 3. App theme from API 27 onwards

Display cutout adjustment (Optional)

If your app is supporting just portrait orientation then you can skip this section. However, if it’s supporting landscape orientation as well (like this app), then we should address the display cutout case as well.

Did you find anything odd in the Fig 3 above?

Well, may be you didn’t because the layout is not obstruct by display cutout. However, I found it odd and maybe it will look odd to you as well. Specifically in the landscape orientation where AppBarLayout bottom shadow or the list item separator lines are not edge-to-edge.

However, just to assure I am not the only one find it odd. I looked at some major apps like Google Play, Gmail & Whatsapp and found two different approaches they applied to handle it.

  • Google Play & Whatsapp (as of now) are handling it by making it completely black.
  • Gmail on the side made adjustment to their UI to make it look more consistent with it.

So, this insight made one thing very clear that we should handle the display cutout even though approach might be different for different apps.

For this app, I’ve proceeded with the first approach as it’s quick. However, I would recommend second approach as it’s more align with edge-to-edge adaption though might require some major UI refactoring.

To apply it, we going to modify the Solution #2 of Status Bar (as discussed in Part-I of this article). We will add an additional parameter includeDisplayCutout inside the function to handle this scenario conditionally.

fun applyEdgeToEdgeOnRV(rootView: View, includeDisplayCutout: Boolean) {
ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, windowInsets ->
val bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
if (!includeDisplayCutout){
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
// Clear existing overlay
v.overlay.clear()
//Apply new overlay
if (cutoutInsets.left > 0 || cutoutInsets.right > 0) {
v.post {
val scrim = Color.BLACK.toDrawable()
if (cutoutInsets.left > 0) {
scrim.setBounds(0, 0, cutoutInsets.left, v.height)
} else {
scrim.setBounds(v.width - cutoutInsets.right, 0, v.width, v.height)
}
v.overlay.add(scrim)
}
}
}
//Apply insets
v.updatePadding(
top = bars.top,
left = bars.left,
right = bars.right
)
windowInsets
}
}

 

The above function will add a black scrim as an overlay above the display cutout area when includeDisplayCutout=false pass in it which will make the layout consistent, as shown in Fig 4.

Fig 4. Black scrim over display cutout area in landscape mode

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

The above optional changes have eventually helped me making the app layout consistent and provide an immersive experience for the users.

If you would like to see these change in action, you can download the iHackerNews app.

Feel free to comment, If you have any suggestions or improvements on this article. I’ll appreciate a clap if you find it useful. Also, follow me for more such updates.

This article was previously published on proandroiddev.com

Menu