Android M Light and Dark status bar programmatically - 如何让它再次变暗?

Android M Light and Dark status bar programmatically - how to make it dark again?

在 Android M 中,我们可以使状态栏图标变暗。为此,我们可以在主题的 xml:

中指定属性
<item name="android:windowLightStatusBar">true</item>

或者我们在运行时用这段代码设置它:

View someView = findViewById(R.id.some_view);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    someView.setSystemUiVisibility(someView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}

而且它确实运行良好。但问题是如何在运行时正确地将状态栏模式设置为暗?

我已经尝试过这些变体:

// Makes status bar mode dark, but also hides it along with all navigation views. 
someView.setSystemUiVisibility(someView.getSystemUiVisibility() | ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

// Does nothing 
someView.setSystemUiVisibility(someView.getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

// Also does nothing 
someView.setSystemUiVisibility(someView.getSystemUiVisibility() ^ View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

那么如何才能正确完成呢?

根据 Nick Butcher 的项目“格子呢”

public static void clearLightStatusBar(@NonNull View view) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        int flags = view.getSystemUiVisibility();
        flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
        view.setSystemUiVisibility(flags);
    }
}

您可以找到此文件 here

@Aracem 发布的解决方案有效,但如果您尝试更改状态栏的 背景颜色 则不起作用。就我而言,我按以下方式进行。

启用 windowLightStatusBar(以编程方式,例如在 Utils class 内):

 public static void setLightStatusBar(View view,Activity activity){


            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

                int flags = view.getSystemUiVisibility();
                flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
                view.setSystemUiVisibility(flags);
                activity.getWindow().setStatusBarColor(Color.WHITE); 
            }
}

要将 StatusBar 恢复到之前的状态:

  public static void clearLightStatusBar(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            Window window = activity.getWindow();
            window.setStatusBarColor(ContextCompat
                 .getColor(activity,R.color.colorPrimaryDark)); 
        }
    }

恢复状态栏的颜色就够了,它还恢复了图标的颜色。 非常重要:在 setLightStatusBar(View view..) 中使用的视图从屏幕上消失(即 view.getVisibility()==GONE|INVISIBLE)之前,不会发生恢复操作。

我会在上面的答案中做一些修改。

做一个class

 public class DarkStatusBar {
    public static void setLightStatusBar(View view, Activity activity){

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

            int flags = view.getSystemUiVisibility();
            flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
            view.setSystemUiVisibility(flags);
            activity.getWindow().setStatusBarColor(Color.WHITE);
        }
    }
}

并像这样在任何地方调用它

        Window window = getWindow();
        View view = window.getDecorView();
        DarkStatusBar.setLightStatusBar(view,this);

我基于@Aracem 和@Carlos Hernández Gil 但我认为如果我们使用 按位 XOR (^ 会更容易理解Java)

中的运算符
private void setLightStatusBar(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        int flags = activity.getWindow().getDecorView().getSystemUiVisibility(); // get current flag
        flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;   // add LIGHT_STATUS_BAR to flag
        activity.getWindow().getDecorView().setSystemUiVisibility(flags); 
        activity.getWindow().setStatusBarColor(Color.GRAY); // optional
    }
}

private void clearLightStatusBar(Activity activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        int flags = activity.getWindow().getDecorView().getSystemUiVisibility(); // get current flag
        flags = flags ^ View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; // use XOR here for remove LIGHT_STATUS_BAR from flags
        activity.getWindow().getDecorView().setSystemUiVisibility(flags);
        activity.getWindow().setStatusBarColor(Color.GREEN); // optional
    }
}

解释

首先,查看SYSTEM_UI_FLAG_LIGHT_STATUS_BARsetSystemUiVisibility

/**
 * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
 * is compatible with light status bar backgrounds.
 */
public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;

public void setSystemUiVisibility(int visibility) {
    if (visibility != mSystemUiVisibility) {
        mSystemUiVisibility = visibility;
        ...
    }
}

我觉得下面两行代码比较难懂

flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; // for set light status bar
flags = flags ^ View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; // for clear light status bar

乍一看,我只是觉得我们可以使用简单的like

flags = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; // for set light status bar
flags = 0; // for clear light status bar (0 <=> LIGHT_STATUS_BAR <=> default systemUiVisibility)

但我们应该使用 |^ 因为
例如,我们要将状态栏和导航栏都设置为亮灯,那么我们将使用

flags = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR | View.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
activity.getWindow().getDecorView().setSystemUiVisibility(flags);

当我们不想让状态栏变亮时,我们可以使用

flags = View.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
activity.getWindow().getDecorView().setSystemUiVisibility(flags);

flags = activity.getWindow().getDecorView().getSystemUiVisibility();
flags = flags ^ View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 
activity.getWindow().getDecorView().setSystemUiVisibility(flags);

要了解更多我们使用 |^ 的原因,我认为下面的教程可能会有所帮助 https://medium.com/@JakobUlbrich/flag-attributes-in-android-how-to-use-them-ac4ec8aee7d1 这是我的理解。希望这有帮助

我整理了这个简单的实用程序对象,它允许您在任何片段中更改状态栏颜色和亮起状态栏 on/off。但是,这依赖于使用 Android Jetpack Navigation 组件进行导航 (Kotlin):

object StatusBarUtil {
    fun changeStatusBarColor(activity: Activity, @ColorInt color: Int, lightStatusBar: Boolean) {
        activity.window?.let { win ->
            val nav = Navigation.findNavController(activity, R.id.your_nav_host_fragmen /* TODO: Use the ID of your nav host fragment */)
            val currentDest = nav.currentDestination?.id
            val oldColor = win.statusBarColor
            val oldFlags = win.decorView.systemUiVisibility
            win.statusBarColor = color

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                var flags = oldFlags
                flags = if (lightStatusBar) {
                    flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
                } else {
                    flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
                }
                win.decorView.systemUiVisibility = flags
            }

            nav.addOnNavigatedListener { _, dest ->
                if (dest.id != currentDest) {
                    win.statusBarColor = oldColor
                    win.decorView.systemUiVisibility = oldFlags
                }
            }
        }
    }
}

要使用它,请从任何片段的 onViewCreated 中调用以下命令:

StatusBarUtil.changeStatusBarColor(requireActivity(), someDarkColor, false)

根据@phan-van-linh 的回答,我为 Xamarin Android

写了这个 class
public static class ActivityExtensions
{
    public static void SetLightStatusBar(this Activity activity)
    {
        int flags = (int)activity.Window.DecorView.SystemUiVisibility; // get current flag
        flags |= (int)SystemUiFlags.LightStatusBar;   // add LIGHT_STATUS_BAR to flag
        activity.Window.DecorView.SystemUiVisibility = (StatusBarVisibility)flags;
        //activity.Window.SetStatusBarColor(Color.GRAY); // optional
    }

    public static void ClearLightStatusBar(this Activity activity)
    {
        int flags = (int)activity.Window.DecorView.SystemUiVisibility; // get current flag
        flags = flags ^ (int)SystemUiFlags.LightStatusBar; // use XOR here for remove LIGHT_STATUS_BAR from flags
        activity.Window.DecorView.SystemUiVisibility = (StatusBarVisibility)flags;
        //activity.Window.setStatusBarColor(Color.GREEN); // optional
    }
}

要更改为浅色状态栏,请使用:-

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 
     activity?.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR

要改回深色状态栏:-

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 
     activity?.window?.decorView?.systemUiVisibility = 0

设置蓝色背景状态栏,文字颜色浅kotlin版本

fun setBlueStatusBarColor(window: Window, context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            window.statusBarColor = context.getColor(R.color.colorBlue)
        }else {
            window.statusBarColor = context.resources.getColor(R.color.colorBlue)
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            var flags: Int = window.decorView.systemUiVisibility
            flags = flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            window.decorView.systemUiVisibility = flags
        }
    }
}

SDK的API30有细微的变化,现在灯光状态栏外观由WindowInsetsController, which can be obtained from a Window控制。 下面是 Kotlin 中的示例方法(在 Activity 内),将新的 API 与之前使用的 View.setSystemUiVisibility for older Android SDK versions. Bear in mind that this only changes the system icons appearance of the status bar and the actual color of the status bar can still be set by Window.setStatusBarColor.

相结合
@Suppress("DEPRECATION")
private fun setSystemUiLightStatusBar(isLightStatusBar: Boolean) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val systemUiAppearance = if (isLightStatusBar) {
                WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
            } else {
                0
            }
            window.insetsController?.setSystemBarsAppearance(systemUiAppearance,
                                                             WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS)
        } else {
            val systemUiVisibilityFlags = if (isLightStatusBar) {
                window.decorView.systemUiVisibility or SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
            } else {
                window.decorView.systemUiVisibility and SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
            }
            window.decorView.systemUiVisibility = systemUiVisibilityFlags
        }
    }
}
/**
 * Changes color of the status bar icons
 * @param isLight if true - shows dark icons, light else
 */
fun setStatusBarUiTheme(activity: Activity?, isLight: Boolean) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        activity?.window?.decorView?.let {
            it.systemUiVisibility = if (isLight)
                it.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR // dark icons
            else
                it.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() // light icons
        }
    }
}

在res/styles.xml

<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
    <item name="android:windowLightStatusBar">true</item>
    .......
</style>

<style name="AppTheme.DarkStatus" parent="AppTheme" tools:targetApi="23" >
    <item name="android:windowLightStatusBar">false</item>
    <item name="android:statusBarColor" >@color/status_bar_color</item>
</style>

在代码中

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setTheme(R.style.AppTheme_DarkStatus);  //To set DarkStatusBar theme
    setContentView(R.layout.activity_drawer);
    ....
}

我在 APIs 23-30 中切换明暗的方式与这些略有不同。这是一个科特林版本

由于我使用 Compose 和 Crossfade 动画来更改主题,在某些情况下会调用此函数两次,因此 xor 撤消自身。另一种方法是反向 or 操作。我的轻主题切换器最终看起来像这样

@Suppress("DEPRECATION")
fun invertInsets(darkTheme: Boolean, window: Window) {
    if (Build.VERSION.SDK_INT >= 30) {
        //Correct way of doing things
        val statusBar = APPEARANCE_LIGHT_STATUS_BARS
        val navBar = APPEARANCE_LIGHT_NAVIGATION_BARS
        if (!darkTheme) {
            window.insetsController?.setSystemBarsAppearance(statusBar, statusBar)
            window.insetsController?.setSystemBarsAppearance(navBar, navBar)
        } else {
            window.insetsController?.setSystemBarsAppearance(0, statusBar)
            window.insetsController?.setSystemBarsAppearance(0, navBar)
        }
    } else {
        // Does bitwise operations (or to add, inverse or to remove)
        // This is depreciated but the new version is API 30+ so I should have this here
        val flags = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or
            if (Build.VERSION.SDK_INT >= 26) View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR else 0

        if (!darkTheme) {
            window.decorView.systemUiVisibility = 
                window.decorView.systemUiVisibility or flags
        } else {
            window.decorView.systemUiVisibility = 
                (window.decorView.systemUiVisibility.inv() or flags).inv()
        }
    }
}

API 30+ 的位是没有折旧的,但实际上没有多少手机在 API 30 所以还有较低的位 APIs

为了简洁起见,它只是预先计算标志(因为设置 LIGHT_NAVIGATION_BARS 是 API 26+),然后明确设置或重置这些确切的标志。没有 andxor 有趣的生意。 or 将始终将标志设置为 1,而逆或事物将始终将标志设置为 0。然而,这是可能的,因为 SYSTEM_UI_FLAG_LIGHT_STATUS_BARSYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR 都是一位。否则它可能需要使用 xor.

对我有用

fun Activity.clearLightStatusBar() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val window = window
        window.statusBarColor = ContextCompat
            .getColor(this, R.color.ultramarine_blue)
    }
}

systemUiVisibility - 现在已弃用。您可以改用 WindowInsetsControllerCompat

private val insetsController: WindowInsetsControllerCompat? by lazy {
    activity?.window?.let { window -> WindowInsetsControllerCompat(window, window.decorView) }
}

private fun setLightStatusBar(light: Boolean) {
    insetsController?.isAppearanceLightStatusBars = light
}