Search code examples
androidkotlinandroid-statusbarandroid-actionmode

How to change status bar color in Android 15 during ActionMode


I'm using

'com.google.android.material:material:1.12.0'

and

'androidx.appcompat:appcompat:1.7.0'

I have a fragment which contains a recycler view. When I select an item (long press) in the RecyclerView I enter ActionMode and show a different toolbar with actions specific to the chosen item(s)

I encountered an issue in Android 15, that when the app enters ActionMode, the status bar changes color from my expected blue to black

So I looked up various solutions here on SO for changing the status bar color in Android 15, and tried to implement them. My solution is something like this:

override fun onCreateActionMode(
    mode: ActionMode?, menu: Menu?
): Boolean {
    val inflater: MenuInflater = mode?.menuInflater ?: return true
    inflater.inflate(R.menu.multi_selection_menu, menu)
    multiSelectionMenu = menu
    // Deactivate PullToRefresh
    setPullToRefreshActivation(false)
    // some other buttons hid...
    val color = getColor(R.color.bar_background)
    setStatusBarColor(color, true)
    return true
}

override fun onDestroyActionMode(mode: ActionMode?) {
    // Activate PullToRefresh
    setPullToRefreshActivation(true)
    // Show hidden buttons...
    // finished multi selection
    detectedTagsAdapter.apply {
        disableMultiSelect()
        clearSelectedItems()
        notifyDataSetChanged()
    }

    val color = getColor(R.color.bar_background)
    setStatusBarColor(color, false)
}

private fun setStatusBarColor(color: Int, on: Boolean) {
    val window = requireActivity().window
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
        window.decorView.setOnApplyWindowInsetsListener { view, insets ->
            val statusBarInsets = insets.getInsets(WindowInsets.Type.statusBars())
            view.setBackgroundColor(color)

            // Adjust padding to avoid overlap
            if (on) {
//                view.setPadding(0, statusBarInsets.top, 0, 0)
            } else {
                view.setPadding(0, 0, 0, 0)
            }

//            insets.consumeSystemWindowInsets() // makes both bars appear
            insets.consumeStableInsets() //leaves a black bar
        }
    } else {
        window.statusBarColor = color
    }
}

So - I have been tinkering with this to try to make it work. If you see the commented lines in the function setStatusBarColorit basically summarizes where I have got to:

  1. If I don't have this function, then in Android 15 the Status bar goes black instead of the color I want (blue) - but the toolbar changes to the ActionBar
  2. If I don't call any of the consume functions (so the last line in the setOnApplyWindowIsetsListener is just insets) then similarly the status bar goes black - but the toolobar changes to the ActionBar
  3. Similarly if I use insets.consumeStableInsets() I still get a black status bar
  4. If I use insets.consumeSystemWindowInsets() then the status bar is overwritten by the the action bar (can't use the action bar because the status bar gets the touches) and the regular toolbar is still visible
  5. If, while using insets.consumeSystemWindowInsets() I add in the code to put in the statusbarInserts top padding, then the status bar keeps its color (hooray!) BUT I now see BOTH the action bar AND then below it the original toolbar (which should be superseded by the action bar

The documentation on this is extremely weak, can anyone explain why in Android 15 the ActionBar doesn't simply take the place of the regular toolbar? It works fine in Android 14 and below.

Any help would be appreciated.

I'm sure in principle I should move all my UI over to Compose, but this is a huge legacy project and I don't have the leisure to rewrite the entire UI just because one screen has a bug in Android 15


Solution

  • This was a problem for me as well couldn't figure it out. Finally found a way via a different Stackoverflow post.

    appcompat-v7 v23.0.0 statusbar color black when in ActionMode

    Here is what worked for mem put these in colors.xml

    <color name="abc_decor_view_status_guard" tools:override="true">@color/colorAccent</color>
    <color name="abc_decor_view_status_guard_light" tools:override="true">@color/colorAccent</color>
    

    Update

    Ok so while the above allows you to change the status bar color during Action Mode it does have some drawbacks, it causes the status bar color to change immediately while the toolbar fades in and out. It looks quite bad in my opinion. I worked up a better way to do this. It might be a bit hacky but I couldn't figure any other way to it. It does work and looks much better. The status bar and toolbar will now fade in and out at the same rate looking much more natural.

    First put the two colors below in your colors.xml file, this will override the Action Bar status guard making it transparent instead of black.

    <color name="abc_decor_view_status_guard" tools:override="true">@android:color/transparent</color>
    <color name="abc_decor_view_status_guard_light" tools:override="true">@android:color/transparent</color>
    

    Next add a View above your toolbar in xml, I called mine fake_status_guard, this will kind of be like your own status guard. Set the alpha to 0.0 so it normally cannot be seen when the activity is created.

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        
        <View
            android:id="@+id/fake_status_guard"
            android:layout_width="match_parent"
            android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
            android:background="@color/colorRedButton"
            android:alpha="0.0" />
    
        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent">
    
            <com.google.android.material.appbar.MaterialToolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimaryDark"
                app:navigationIcon="?attr/homeAsUpIndicator"
                app:titleTextAppearance="@style/Text20AllerBold" />
    
        </com.google.android.material.appbar.AppBarLayout>
    
        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/fragment_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@+id/app_bar" />
    
    </RelativeLayout>
    

    Then in your activity that is using Action Mode programmatically set the height of this view in onCreate to the height of the status bar via setOnApplyWindowInsetsListener like below

    ViewCompat.setOnApplyWindowInsetsListener(binding.fakeStatusGuard, (v, windowInsets) -> {
                Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars());
                v.getLayoutParams().height = insets.top;
                v.setLayoutParams(v.getLayoutParams());
                return windowInsets;
            });
    

    You will now need to fade the custom status guard in and out when Action Mode starts and stops. Add the below to your Activity that is using Action Mode

        @Override
        public void onActionModeStarted(ActionMode mode) {
            super.onActionModeStarted(mode);
            binding.fakeStatusGuard.animate().alpha(1f);
        }
    
        @Override
        public void onActionModeFinished(ActionMode mode) {
            super.onActionModeFinished(mode);
            binding.fakeStatusGuard.animate().alpha(0f);
        }
    

    This fades the new view in and out along with the action bar making the animation look much better.