窗口模糊

在 Android 12 中,公用 API 可用於實現視窗模糊效果,例如背景模糊和後面模糊。

視窗模糊或跨視窗模糊用於模糊給定視窗後面的螢幕。視窗模糊有兩種類型,可用於實現不同的視覺效果:

  • 背景模糊可讓您建立背景模糊的窗口,從而產生毛玻璃效果。

  • 後面模糊可讓您模糊(對話方塊)視窗後面的整個螢幕,從而創建景深效果。

兩種效果可以單獨使用,也可以組合使用,如下圖:

僅背景模糊

A

僅模糊後面

背後模糊和背景模糊

C

圖 1.僅背景模糊 (a)、僅背景模糊 (b)、背景模糊和背景模糊 (c)

視窗模糊功能可以跨視窗工作,這意味著當視窗後面有另一個應用程式時它也可以工作。此效果與模糊渲染效果不同,模糊渲染效果會模糊同一視窗內的內容。視窗模糊對於對話方塊、底部工作表和其他浮動視窗非常有用。

執行

應用程式開發人員

應用程式開發人員必須提供模糊半徑才能創建模糊效果。模糊半徑控制模糊的密集程度,即半徑越大,模糊越密集。 0 px 的模糊意味著沒有模糊。對於後方模糊,20 px 的半徑可以產生良好的景深效果,而 80 px 的背景模糊半徑可以產生良好的磨砂玻璃效果。避免模​​糊半徑高於 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 版本:Windows 模糊 API 僅在 Android 12 及更高版本上可用。檢查Android版本的設備SDK。

  • 圖形效能:GPU 效能較低的裝置可能會選擇不支援視窗模糊。

  • 系統狀態:系統伺服器可能會在運行時暫時停用視窗模糊,例如,在省電模式下、播放某些類型的影片內容時或由於開發人員覆蓋。

為了使您的應用程式能夠跨 Android 版本、裝置和系統狀態相容,請遵循以下準則:

  • 透過WindowManager#addCrossWindowBlurEnabledListener新增監聽器,以便在啟用或停用視窗模糊時通知您。此外,使用WindowManager#isCrossWindowBlurEnabled查詢目前是否啟用了視窗模糊。

  • 為視窗背景實作兩個版本,以適應視窗模糊的啟用或停用狀態。

    啟用模糊後,視窗背景應該是半透明的,以使模糊可見。在此狀態下,當停用模糊時,視窗內容會直接與底層視窗的內容重疊,從而使重疊視窗不太清晰。為了避免這種效果,當停用視窗模糊時,請按如下方式調整應用程式的 UI:

    • 對於背景模糊,增加視窗背景可繪製的 Alpha,使其更不透明。

    • 對於後面的模糊,請添加具有更高暗淡量的暗淡圖層。

背後模糊和背景模糊的範例

本節提供了同時使用後面模糊和背景模糊的活動的工作範例。

以下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>

視窗模糊模糊了活動下方視窗的內容。模糊影像是在此活動視窗繪製的,因此活動視窗需要是半透明的,以允許模糊可見。為了讓視窗半透明,我們在活動主題中設定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 虛擬裝置都支援視窗模糊。

無圓角

更新開發者選項不會啟用模糊

  • 檢查設備是否處於省電模式或是否正在使用多媒體隧道。在某些電視設備上,視訊播放期間也可能會停用視窗模糊。

全螢幕繪製背景模糊,不在視窗範圍內

來自偵聽器的更新不會套用到螢幕上

  • 偵聽器更新可能會套用於舊視窗實例。檢查視窗是否被破壞並使用正確的偵聽器更新重新建立。