【问题标题】:How to change status bar color on entering contextual action mode如何在进入上下文操作模式时更改状态栏颜色
【发布时间】:2021-08-19 05:35:10
【问题描述】:

我的应用程序使用继承 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 文件应用程序)执行此操作的示例:

注意它如何正确更改状态栏背景颜色以及相应的图标颜色。

我已经尝试过this 的回答,但正如许多用户在那里评论的那样,它不会导致顺利过渡。是否有任何标准方法来实现所需的行为?

【问题讨论】:

  • 从 Google 文件 GIF 的外观来看,它看起来根本不是两个单独的颜色变化,它看起来是一个(现场检查,它是一个)。一方面,我认为你可以使用自定义Toolbar 并在StatusBar 后面画图,或者第二,将fitSystemWindows 设置为false,将ToolbarpaddingTop 设置为StatusBar's height 基本上会增加其高度以覆盖整个区域。第一个是矫枉过正但可以控制,第二个是hacky但有效。

标签: android android-actionbar android-toolbar contextual-action-bar


【解决方案1】:

显示/隐藏上下文操作栏 (CAB) 时有两个请求:

  1. 切换状态栏图标灯模式。
  2. 在 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()
}

这需要在onCreateActionModeonDestroyActionMode 中使用适当的颜色来调用:

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,对于 API 级别 30 及以上,请使用 WindowInsetsController

由于某种原因,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 隐藏,所以动画仍然被消耗但由于设置了 alpha 而不可见。这需要您在第一种方法中假定为 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)

【讨论】:

  • 在第二种方法中,你能解释一下将自定义视图父级的 alpha 设置为 0 的目的吗?我测试它工作正常但不确定如何:-)
  • @TusharKathuria 我试图让它隐藏起来直到动画开始(将 alpaha 设置为 0 视图是完全透明的,然后在动画结束时它变得完全不透明)......我认为(mode?.customView?.parent as View).alpha = 0f 也可以。
【解决方案2】:

这段代码 sn-p 将改变背景和标题颜色

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>

【讨论】:

  • 这个问题是关于如何在确保过渡平滑的同时更改状态栏颜色和工具栏颜色。这不是关于如何更改标题文本颜色
【解决方案3】:
  1. 你可以试试这个:

Change status bar color with AppCompat ActionBarActivity

  1. 或尝试以编程方式将不同的主题应用于已应用状态栏颜色的活动。
<resources>
    <style name="AppTheme" parent="AppTheme.Base">
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-08
    • 2020-06-23
    • 1970-01-01
    • 1970-01-01
    • 2020-08-18
    • 2019-01-13
    • 1970-01-01
    • 2019-10-27
    相关资源
    最近更新 更多