如何在进入上下文操作模式时更改状态栏颜色
How to change status bar color on entering contextual action mode
我的应用使用了继承Theme.MaterialComponents.Light.NoActionBar
的应用主题。当用户选择一个列表项并进入上下文操作模式时,我想将操作栏更改为深灰色。我正在使用以下代码来实现相同的目的:
在themes.xml
中:
<item name="windowActionModeOverlay">true</item>
<item name="actionModeStyle">@style/Widget.App.ActionMode</item>
<item name="actionModeCloseDrawable">@drawable/ic_close_24dp</item>
<item name="actionBarTheme">@style/ThemeOverlay.MaterialComponents.Dark.ActionBar</item>
在styles.xml
中:
<style name="Widget.App.ActionMode" parent="Widget.AppCompat.ActionMode">
<item name="background">@color/grey_100</item>
</style>
在我的片段中:
val callback = object: ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
requireActivity().menuInflater.inflate(R.menu.contextual_action_bar, menu)
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?) {}
}
val actionMode = requireActivity().startActionMode(callback)
actionMode?.title = "1 selected"
使用这段代码,我得到以下结果:
我们如何更改此代码以使其也更改状态栏背景和图标颜色?以下是有多少应用程序(包括 Google 文件应用程序)执行此操作的示例:
注意它如何正确地更改状态栏背景颜色以及相应的图标颜色。
我已经尝试了 答案,但它并没有像许多用户在那里评论的那样顺利过渡。是否有任何标准方法可以实现所需的行为?
此代码段将更改背景和标题颜色
在styles.xml
<style name="Widget.App.ActionMode" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/grey_100</item>
<item name="titleTextStyle">@style/ActionModeTitleTextStyle</item>
</style>
<style name="ActionModeTitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionMode.Title">
<item name="android:textColor">@android:color/white</item>
</style>
上下文操作栏(CAB)为show/hidden时有两个请求:
- 切换状态栏图标灯模式。
- 同步 CAB 和状态栏之间的彩色动画。
在 CAB 和状态栏之间同步彩色动画
您可以使用 ArgbEvaluator
调整状态栏颜色变化的动画效果,调整后的持续时间趋于 CAB 持续时间(经过反复试验,它接近 300 毫秒;我没有确切值的文档线索, 但您可以根据需要进行调整):
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()
}
这需要在 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
)
}
}
}
切换状态栏图标灯模式
对于API 30级以下(Android R),使用systemUiVisibility
,WindowInsetsController
对于API 30级及以上:
出于某种原因,WindowInsetsController
对我不起作用,即使使用以下 4 个版本,但幸好旧标志有效,所以我将其保持在 API 级别 > 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
}
}
因此,工作演示:
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
}
}
}
预览:
更新
另一种方法
您可以禁用动画来代替动画状态栏以与 CAB 同步。但这要求您对 CAB 使用 customView 而不是菜单。
有两个地方可以做到这一点:
显示 CAB 时
每当你调用 startSupportActionMode
:
val mode = startSupportActionMode(callback)
ViewCompat.animate(mode?.customView?.parent as View).alpha(0f)
隐藏CAB时:
在onDestroyActionMode
中完成
override fun onDestroyActionMode(mode: ActionMode?) {
// Hiding the CAB
(mode?.customView?.parent as View).visibility = View.GONE
}
缺点是没有动画了,显示CAB有延迟,因为它只是使用alpha隐藏,所以动画仍然被消耗但因为设置了alph而看不见。这要求您在第一种方法中假定为 300 毫秒的延迟后切换状态栏颜色:
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)
- 你可以试试这个:
Change status bar color with AppCompat ActionBarActivity
- 或者尝试以编程方式将不同的主题应用到已应用状态栏颜色的 activity。
<resources>
<style name="AppTheme" parent="AppTheme.Base">
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>
我的应用使用了继承Theme.MaterialComponents.Light.NoActionBar
的应用主题。当用户选择一个列表项并进入上下文操作模式时,我想将操作栏更改为深灰色。我正在使用以下代码来实现相同的目的:
在themes.xml
中:
<item name="windowActionModeOverlay">true</item>
<item name="actionModeStyle">@style/Widget.App.ActionMode</item>
<item name="actionModeCloseDrawable">@drawable/ic_close_24dp</item>
<item name="actionBarTheme">@style/ThemeOverlay.MaterialComponents.Dark.ActionBar</item>
在styles.xml
中:
<style name="Widget.App.ActionMode" parent="Widget.AppCompat.ActionMode">
<item name="background">@color/grey_100</item>
</style>
在我的片段中:
val callback = object: ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
requireActivity().menuInflater.inflate(R.menu.contextual_action_bar, menu)
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?) {}
}
val actionMode = requireActivity().startActionMode(callback)
actionMode?.title = "1 selected"
使用这段代码,我得到以下结果:
我们如何更改此代码以使其也更改状态栏背景和图标颜色?以下是有多少应用程序(包括 Google 文件应用程序)执行此操作的示例:
注意它如何正确地更改状态栏背景颜色以及相应的图标颜色。
我已经尝试了
此代码段将更改背景和标题颜色
在styles.xml
<style name="Widget.App.ActionMode" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/grey_100</item>
<item name="titleTextStyle">@style/ActionModeTitleTextStyle</item>
</style>
<style name="ActionModeTitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionMode.Title">
<item name="android:textColor">@android:color/white</item>
</style>
上下文操作栏(CAB)为show/hidden时有两个请求:
- 切换状态栏图标灯模式。
- 同步 CAB 和状态栏之间的彩色动画。
在 CAB 和状态栏之间同步彩色动画
您可以使用 ArgbEvaluator
调整状态栏颜色变化的动画效果,调整后的持续时间趋于 CAB 持续时间(经过反复试验,它接近 300 毫秒;我没有确切值的文档线索, 但您可以根据需要进行调整):
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()
}
这需要在 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
)
}
}
}
切换状态栏图标灯模式
对于API 30级以下(Android R),使用systemUiVisibility
,WindowInsetsController
对于API 30级及以上:
出于某种原因,WindowInsetsController
对我不起作用,即使使用以下 4 个版本,但幸好旧标志有效,所以我将其保持在 API 级别 > 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
}
}
因此,工作演示:
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
}
}
}
预览:
更新
另一种方法
您可以禁用动画来代替动画状态栏以与 CAB 同步。但这要求您对 CAB 使用 customView 而不是菜单。
有两个地方可以做到这一点:
显示 CAB 时
每当你调用
startSupportActionMode
:
val mode = startSupportActionMode(callback)
ViewCompat.animate(mode?.customView?.parent as View).alpha(0f)
隐藏CAB时:
在
中完成onDestroyActionMode
override fun onDestroyActionMode(mode: ActionMode?) {
// Hiding the CAB
(mode?.customView?.parent as View).visibility = View.GONE
}
缺点是没有动画了,显示CAB有延迟,因为它只是使用alpha隐藏,所以动画仍然被消耗但因为设置了alph而看不见。这要求您在第一种方法中假定为 300 毫秒的延迟后切换状态栏颜色:
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)
- 你可以试试这个:
Change status bar color with AppCompat ActionBarActivity
- 或者尝试以编程方式将不同的主题应用到已应用状态栏颜色的 activity。
<resources> <style name="AppTheme" parent="AppTheme.Base"> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources>