窗口模糊

在 Android 12 中,公共 API 可用于实现窗口模糊效果,例如背景模糊和后方模糊。

窗口模糊(或跨窗口模糊)用于模糊给定窗口后面的屏幕。有两种类型的窗口模糊,可用于实现不同的视觉效果

  • 背景模糊允许您创建具有模糊背景的窗口,从而营造出磨砂玻璃效果。

  • 后方模糊允许您模糊(对话框)窗口后面的整个屏幕,从而营造出景深效果。

这两种效果可以单独使用或组合使用,如下图所示

background blur only

a

blur behind only

b

blur behind and background blur

c

图 1. 仅背景模糊 (a)、仅后方模糊 (b)、背景模糊和后方模糊 (c)

窗口模糊功能跨窗口工作,这意味着当您的窗口后面有另一个应用时,它也有效。此效果与模糊渲染效果不同,后者模糊的是同一窗口内部的内容。窗口模糊对于对话框、底部工作表和其他浮动窗口非常有用。

实现

应用开发者

应用开发者必须提供模糊半径才能创建模糊效果。模糊半径控制模糊的密度,即半径越高,模糊越密集。0 像素的模糊意味着不模糊。对于后方模糊,20 像素的半径可营造出良好的景深效果,而 80 像素的背景模糊半径可营造出良好的磨砂玻璃效果。避免使用高于 150 像素的模糊半径,因为这会显著影响性能。

为了实现所需的模糊效果并提高可读性,请选择与半透明颜色层互补的模糊半径值。

背景模糊

在浮动窗口上使用背景模糊以创建窗口背景效果,该效果是底层内容的模糊图像。要为您的窗口添加模糊背景,请执行以下操作

  1. 调用 Window#setBackgroundBlurRadius(int) 以设置背景模糊半径。或者,在窗口主题中,设置 R.attr.windowBackgroundBlurRadius

  2. R.attr.windowIsTranslucent 设置为 true 以使窗口变为半透明。模糊在窗口表面下方绘制,因此窗口需要是半透明的才能使模糊可见。

  3. (可选)调用 Window#setBackgroundDrawableResource(int) 以添加带有半透明颜色的矩形窗口背景可绘制资源。或者,在窗口主题中,设置 R.attr.windowBackground

  4. 对于具有圆角的窗口,通过将具有圆角ShapeDrawable 设置为窗口背景可绘制资源,来确定模糊区域的圆角。

  5. 处理模糊启用和停用状态。请参阅应用中使用窗口模糊的指南部分,了解更多信息。

后方模糊

窗口后的模糊效果会模糊窗口后面的整个屏幕。此效果用于将用户的注意力引导到窗口内容,方法是模糊窗口后面屏幕上的任何内容。

要模糊窗口后面的内容,请按照以下步骤操作

  1. FLAG_BLUR_BEHIND 添加到窗口标志,以启用背景模糊。或者,在窗口主题中,设置 R.attr.windowBlurBehindEnabled

  2. 调用 WindowManager.LayoutParams#setBlurBehindRadius 以设置背景模糊半径。或者,在窗口主题中,设置 R.attr.windowBlurBehindRadius

  3. 可选操作:选择一个补充的暗淡度

  4. 处理模糊启用和停用状态。请参阅应用中使用窗口模糊的指南部分,了解更多信息。

在应用中使用窗口模糊效果的指南

对窗口模糊效果的支持取决于以下因素

  • Android 版本:窗口模糊 API 仅在 Android 12 及更高版本上可用。检查设备的 SDK 以了解 Android 版本。

  • 图形性能:GPU 性能较低的设备可能选择不支持窗口模糊效果。

  • 系统状态:系统服务器可能会在运行时临时停用窗口模糊效果,例如,在省电模式期间、播放某些类型的视频内容时或由于开发者替换设置。

为了使您的应用在各种 Android 版本、设备和系统状态下都兼容,请遵循以下指南

  • 通过 WindowManager#addCrossWindowBlurEnabledListener 添加一个监听器,以便在窗口模糊效果启用或停用时通知您。此外,使用 WindowManager#isCrossWindowBlurEnabled 查询当前是否启用了窗口模糊效果。

  • 为窗口背景实现两个版本,以适应窗口模糊效果的启用或停用状态。

    当启用模糊效果时,窗口背景应为半透明,以使模糊效果可见。在这种状态下,当模糊效果被停用时,窗口内容会直接与底层窗口的内容重叠,从而使重叠窗口的可读性降低。为了避免这种效果,当窗口模糊效果被停用时,请按如下方式调整应用的 UI

    • 对于背景模糊,增加窗口背景可绘制对象的 alpha 值,使其更不透明。

    • 对于背景后的模糊,添加一个具有更高暗淡度的暗淡层。

背景后模糊和背景模糊的示例

本节提供了一个工作示例,演示了同时使用背景后模糊和背景模糊的 Activity。

以下 MainActivity.java 示例是一个对话框,其背景后模糊半径为 20 px,背景模糊半径为 80 px。它具有圆角,在窗口背景可绘制对象的 xml 中定义。它可以正确处理不同的 Android 版本、不同的设备(可能不支持窗口模糊效果)以及运行时模糊效果启用或停用更改。它通过调整窗口背景可绘制对象的 alpha 值和窗口暗淡度来确保对话框内容在任何这些条件下都可读。

public class MainActivity extends Activity {

    private final int mBackgroundBlurRadius = 80;
    private final int mBlurBehindRadius = 20;

    // We set a different dim amount depending on whether window blur is enabled or disabled
    private final float mDimAmountWithBlur = 0.1f;
    private final float mDimAmountNoBlur = 0.4f;

    // We set a different alpha depending on whether window blur is enabled or disabled
    private final int mWindowBackgroundAlphaWithBlur = 170;
    private final int mWindowBackgroundAlphaNoBlur = 255;

    // Use a rectangular shape drawable for the window background. The outline of this drawable
    // dictates the shape and rounded corners for the window background blur area.
    private Drawable mWindowBackgroundDrawable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWindowBackgroundDrawable = getDrawable(R.drawable.window_background);
        getWindow().setBackgroundDrawable(mWindowBackgroundDrawable);

        if (buildIsAtLeastS()) {
            // Enable blur behind. This can also be done in xml with R.attr#windowBlurBehindEnabled
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);

            // Register a listener to adjust window UI whenever window blurs are enabled/disabled
            setupWindowBlurListener();
        } else {
            // Window blurs are not available prior to Android S
            updateWindowForBlurs(false /* blursEnabled */);
        }

        // Enable dim. This can also be done in xml, see R.attr#backgroundDimEnabled
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    }

    /**
     * Set up a window blur listener.
     *
     * Window blurs might be disabled at runtime in response to user preferences or system states
     * (e.g. battery saving mode). WindowManager#addCrossWindowBlurEnabledListener allows to
     * listen for when that happens. In that callback we adjust the UI to account for the
     * added/missing window blurs.
     *
     * For the window background blur we adjust the window background drawable alpha:
     *     - lower when window blurs are enabled to make the blur visible through the window
     *       background drawable
     *     - higher when window blurs are disabled to ensure that the window contents are readable
     *
     * For window blur behind we adjust the dim amount:
     *     - higher when window blurs are disabled - the dim creates a depth of field effect,
     *       bringing the user's attention to the dialog window
     *     - lower when window blurs are enabled - no need for a high alpha, the blur behind is
     *       enough to create a depth of field effect
     */
    @RequiresApi(api = Build.VERSION_CODES.S)
    private void setupWindowBlurListener() {
        Consumer<Boolean> windowBlurEnabledListener = this::updateWindowForBlurs;
        getWindow().getDecorView().addOnAttachStateChangeListener(
                new View.OnAttachStateChangeListener() {
                    @Override
                    public void onViewAttachedToWindow(View v) {
                        getWindowManager().addCrossWindowBlurEnabledListener(
                                windowBlurEnabledListener);
                    }

                    @Override
                    public void onViewDetachedFromWindow(View v) {
                        getWindowManager().removeCrossWindowBlurEnabledListener(
                                windowBlurEnabledListener);
                    }
                });
    }

    private void updateWindowForBlurs(boolean blursEnabled) {
        mWindowBackgroundDrawable.setAlpha(blursEnabled && mBackgroundBlurRadius > 0 ?
                mWindowBackgroundAlphaWithBlur : mWindowBackgroundAlphaNoBlur);
        getWindow().setDimAmount(blursEnabled && mBlurBehindRadius > 0 ?
                mDimAmountWithBlur : mDimAmountNoBlur);

        if (buildIsAtLeastS()) {
            // Set the window background blur and blur behind radii
            getWindow().setBackgroundBlurRadius(mBackgroundBlurRadius);
            getWindow().getAttributes().setBlurBehindRadius(mBlurBehindRadius);
            getWindow().setAttributes(getWindow().getAttributes());
        }
    }

    private static boolean buildIsAtLeastS() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
    }
}

要为窗口创建圆角,我们在 res/drawable/window_background.xml 中将窗口背景定义为 ShapeDrawable,其中 圆角 的半径为 20 dp,如下所示

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
    <corners android:radius="20dp"/>
    <solid android:color="#AAAAAA"/>
</shape>

窗口模糊效果会模糊 Activity 下方窗口的内容。模糊图像绘制在 Activity 窗口下方,因此 Activity 窗口需要是半透明的,以便模糊效果可见。为了使窗口半透明,我们在 Activity 主题中设置 R.attr.windowIsTranslucent,如下所示

<style name="Theme.BlurryDialog" parent="Theme.MaterialComponents.Dialog">
    <item name="android:windowIsTranslucent">true</item>
</style>

OEM 和合作伙伴

要在设备上启用窗口模糊效果,OEM 需要声明设备支持窗口模糊效果。

要检查您的设备是否可以支持窗口模糊效果,请执行以下操作

  • 确保设备可以处理额外的 GPU 负载。低端设备可能无法处理额外的负载,这可能会导致掉帧。仅在经过测试且 GPU 性能足够的设备上启用窗口模糊效果。

  • 如果您有自定义渲染引擎,请确保您的渲染引擎实现了模糊逻辑。默认的 Android 12 渲染引擎BlurFilter.cpp 中实现了模糊逻辑。

一旦您确保您的设备可以支持窗口模糊效果,请设置以下 surface flinger sysprop

PRODUCT_VENDOR_PROPERTIES += \
       ro.surface_flinger.supports_background_blur=1

验证

要验证您的应用窗口在模糊效果启用和停用状态之间切换时是否处理得当,请按照以下步骤操作

  1. 打开具有模糊效果的 UI。

  2. 通过开启和关闭窗口模糊效果来启用或停用窗口模糊效果。

  3. 验证窗口 UI 是否按预期在模糊状态和非模糊状态之间切换。

开启和关闭窗口模糊效果

要测试窗口 UI 在窗口模糊效果下的渲染效果,请使用以下方法之一启用或停用模糊效果

  • 从开发者选项

    设置 -> 系统 -> 开发者选项 -> 硬件加速渲染 -> 允许窗口级模糊

  • 从已 root 设备的终端

    adb shell wm disable-blur 1 # 1 disables window blurs, 0 allows them

要检查您的 Android 12+ 设备是否支持窗口模糊效果以及当前是否启用了窗口模糊效果,请在已 root 的设备上运行 adb shell wm disable-blur

问题排查

在验证期间,请使用以下内容作为问题排查指南。

未绘制模糊效果

  • 验证模糊效果当前是否已启用以及您的硬件是否支持模糊效果。请参阅开启和关闭窗口模糊效果

  • 确保您设置了半透明的窗口背景颜色。不透明的窗口背景颜色会隐藏模糊区域。

测试设备不支持窗口模糊效果

  • 在 Android 12 模拟器上测试您的应用。要设置 Android 模拟器,请参阅设置 Android 模拟器。您使用模拟器创建的任何 Android 虚拟设备都支持窗口模糊效果。

没有圆角

更新开发者选项不会启用模糊效果

  • 检查设备是否处于省电模式,或者是否正在使用多媒体隧道。在某些电视设备上,窗口模糊效果也可能在视频播放期间被停用。

背景模糊效果全屏绘制,而不是在窗口边界内绘制

来自监听器的更新未应用到屏幕上

  • 监听器更新可能正在应用于旧的窗口实例。检查窗口是否正在被销毁并使用正确的监听器更新重新创建。