There are two requests when the contextual action bar (CAB) is show/hidden:
- Toggle status bar icon light mode.
- Synchronize the color animation between the CAB & the status bar.
Sync color animation between CAB & status bar
You can animate the change of the status bar color using ArgbEvaluator
with an adjusted duration that tends to the CAB duration (with trial and error it's near 300 msec; I have no documentation clue for the exact value, but you can adjust that to your needs):
fun switchStatusColor(colorFrom: Int, colorTo: Int, duration: Long) {
val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo)
colorAnimation.duration = duration // milliseconds
colorAnimation.addUpdateListener { animator ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
window.statusBarColor = animator.animatedValue as Int
}
colorAnimation.start()
}
And this need to be called with the appropriate colors within onCreateActionMode
& onDestroyActionMode
:
val callback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
menuInflater.inflate(R.menu.contextual_action_bar, menu)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switchStatusColor(
window.statusBarColor,
ContextCompat.getColor(this@MainActivity, R.color.grey_100), 300
)
}
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return false
}
override fun onDestroyActionMode(mode: ActionMode?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switchStatusColor(
window.statusBarColor,
ContextCompat.getColor(this@MainActivity, R.color.white), 300
)
}
}
}
Toggle status bar icon light mode
For API level below 30 (Android R), use systemUiVisibility
, and WindowInsetsController
for API level 30 and above:
For some reason the WindowInsetsController
didn't work for me, even by using the below 4 versions, but thankfully the old flag works, so I kept it in API level > 30:
private fun switchStatusBarIconLight(isLight: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.setSystemBarsAppearance(
if (isLight) APPEARANCE_LIGHT_STATUS_BARS else 0,
APPEARANCE_LIGHT_STATUS_BARS
)
WindowInsetsControllerCompat(
window,
window.decorView
).isAppearanceLightStatusBars =
isLight
ViewCompat.getWindowInsetsController(window.decorView)?.apply {
isAppearanceLightStatusBars = isLight
}
WindowCompat.getInsetsController(
window,
window.decorView
)?.isAppearanceLightStatusBars = isLight
window.decorView.systemUiVisibility =
if (isLight) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0 // Deprecated in API level 30 // but only works than the above
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (isLight) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0 // Deprecated in API level 30
}
}
So, the working demo:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Setting up the status bar when the app starts
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
window.statusBarColor = ContextCompat.getColor(this@MainActivity, R.color.white)
switchStatusBarIconLight(true)
val callback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
menuInflater.inflate(R.menu.contextual_action_bar, menu)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switchStatusColor(
window.statusBarColor,
ContextCompat.getColor(this@MainActivity, R.color.grey_100), 300
)
}
switchStatusBarIconLight(false)
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return false
}
override fun onDestroyActionMode(mode: ActionMode?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switchStatusColor(
window.statusBarColor,
ContextCompat.getColor(this@MainActivity, R.color.white), 300
)
}
switchStatusBarIconLight(true)
}
}
}
*
* Animate switching the color of the status bar from the colorFrom color to the colorTo color
* duration: animation duration.
* */
private fun switchStatusColor(colorFrom: Int, colorTo: Int, duration: Long) {
val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo)
colorAnimation.duration = duration // milliseconds
colorAnimation.addUpdateListener { animator ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
window.statusBarColor = animator.animatedValue as Int
}
colorAnimation.start()
}
/*
* Switch the dark mode of the status bar icons
* When isLight is true, the status bar icons will turn light
* */
private fun switchStatusBarIconLight(isLight: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.setSystemBarsAppearance(
if (isLight) APPEARANCE_LIGHT_STATUS_BARS else 0,
APPEARANCE_LIGHT_STATUS_BARS
)
WindowInsetsControllerCompat(
window,
window.decorView
).isAppearanceLightStatusBars =
isLight
ViewCompat.getWindowInsetsController(window.decorView)?.apply {
isAppearanceLightStatusBars = isLight
}
WindowCompat.getInsetsController(
window,
window.decorView
)?.isAppearanceLightStatusBars = isLight
window.decorView.systemUiVisibility =
if (isLight) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0 // Deprecated in API level 30 // but only works than the above
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (isLight) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0 // Deprecated in API level 30
}
}
}
Preview:

UPDATE
Another approach
Instead of animating the status bar to sync with the CAB, you can instead disable the animation. But this requires you to use a customView to the CAB instead of a menu.
There are two places to do that:
val mode = startSupportActionMode(callback)
ViewCompat.animate(mode?.customView?.parent as View).alpha(0f)
override fun onDestroyActionMode(mode: ActionMode?) {
// Hiding the CAB
(mode?.customView?.parent as View).visibility = View.GONE
}
The downside is that no animation anymore, and there is a delay to show the CAB because it is just hidden using the alpha, so the animation is still consumed but invisible because of setting the alph. And this requires you to to toggle the status bar color after some delay which is assumed by 300 millisec in the first approach:
val callback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
val customView: View = LayoutInflater.from(this@MainActivity).inflate(
R.layout.custom_contextual_action_bar, null
)
mode?.customView = customView
Handler(Looper.getMainLooper()).postDelayed({
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor =
ContextCompat.getColor(this@MainActivity, R.color.grey_100)
switchStatusBarIconLight(false)
}
}, 300)
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
return false
}
override fun onDestroyActionMode(mode: ActionMode?) {
// Hiding the CAB
(mode?.customView?.parent as View).visibility = View.GONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor =
ContextCompat.getColor(this@MainActivity, R.color.white)
switchStatusBarIconLight(true)
}
}
}
// Call:
val mode = startSupportActionMode(callback)
ViewCompat.animate(mode?.customView?.parent as View).alpha(0f)
