窗口模糊处理

在 Android 12 中,我们提供了一些用于实现窗口模糊处理效果(例如背景模糊处理和模糊处理后方屏幕)的公共 API。

窗口模糊处理或跨窗口模糊处理用于模糊处理给定窗口后方的屏幕。有两种窗口模糊处理方式,可用于实现不同的视觉效果:

  • 背景模糊处理:可用于创建具有模糊背景的窗口,创造出磨砂玻璃效果。

  • 模糊处理后方屏幕:可用于模糊处理(对话框)窗口后方的整个屏幕,创造出景深效果。

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

仅背景模糊处理

a

仅模糊处理后方屏幕

b

模糊处理后方屏幕和背景模糊处理

c

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

窗口模糊处理功能可以跨窗口使用,这意味着当窗口背后有其他应用时,该功能同样可以发挥作用。此效果与模糊处理渲染效果不同,后者会对同一窗口内部的内容进行模糊处理。窗口模糊处理对于对话框、底部动作条和其他浮动窗口非常有用。

实现

应用开发者

为了创建模糊处理效果,应用开发者必须提供一个模糊处理半径。模糊处理半径用于控制模糊处理的密度,即半径越高,模糊处理的密度越大。0 像素的模糊处理表示不进行模糊处理。对于模糊处理后方屏幕,半径达到 20 像素,即可实现良好的景深效果;对于背景模糊处理,半径达到 80 像素,即可实现良好的磨砂玻璃效果。应避免使用超过 150 像素的模糊处理半径,因为这会严重影响性能。

为了达到理想的模糊处理效果并提高可辨别性,请选择与半透明颜色层互补的模糊处理半径值。

背景模糊处理

对浮动窗口使用背景模糊处理可实现窗口背景效果,这是底层内容的模糊处理图像。如需为窗口添加模糊处理的背景,请执行以下操作:

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

  2. R.attr.windowIsTranslucent 设为 true,使窗口变为半透明。模糊处理是在窗口 Surface 下面绘制的,因此窗口必须是半透明的,才能显示出模糊处理效果。

  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 查询当前是否启用了窗口模糊处理。

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

    启用模糊处理后,窗口背景应该是半透明的,以显示出模糊处理效果。如果在此状态下停用模糊处理,窗口内容将与底层窗口的内容直接重叠,重叠后的窗口将不易辨认。为避免出现这种效果,在停用窗口模糊处理时,请按如下方式调整应用的界面:

    • 对于背景模糊处理,请提高窗口背景可绘制对象的 Alpha 值,以降低其透明度。

    • 对于模糊处理后方屏幕,请添加一个暗度更高的暗层。

模糊处理后方屏幕和背景模糊处理的示例

本部分提供了一个同时使用“模糊处理后方屏幕”和“背景模糊处理”的 activity 的实际示例。

以下 MainActivity.java 示例是一个对话框,其“模糊处理后方屏幕”半径为 20 像素,“背景模糊处理”半径为 80 像素。该对话框具有圆角(在窗口背景可绘制对象的 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 中将窗口背景定义为具有半径为 20 dp 的圆角ShapeDrawable,如下所示:

<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. 打开支持模糊处理的界面。

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

  3. 验证窗口界面是否按预期在模糊处理和非模糊处理状态之间转换。

开启和关闭窗口模糊处理

如需测试窗口界面的窗口模糊处理呈现效果,请使用以下方法之一启用或停用模糊处理:

  • 通过“开发者选项”操作:

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

  • 通过已取得 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 虚拟设备都支持窗口模糊处理。

无圆角

更新开发者选项并不会启用模糊处理功能

  • 请检查设备是否处于省电模式或是否正在使用多媒体隧道技术。在某些 TV 设备上,播放视频时也可能会停用窗口模糊处理。

以全屏形式(而不是在窗口边界内)绘制的背景模糊处理

来自监听器的更新内容不会应用到屏幕上

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