ウィンドウのぼかし

Android 12 では、ウィンドウぼかし効果(背景ぼかしと背後ぼかしなど)を実装するための公開 API を利用できます。

ウィンドウぼかし、またはクロス ウィンドウぼかしは、特定のウィンドウの背後にある画面をぼかすために使用されます。ウィンドウぼかしには 2 種類あり、これらを使用してさまざまな視覚効果を実現できます。

  • 背景ぼかしを使用すると、背景をぼかしたウィンドウを作成して、すりガラスの効果を生み出すことができます。

  • 背後ぼかしを使用すると、(ダイアログ)ウィンドウの背後にある画面全体をぼかして、被写界深度の効果を生み出すことができます。

次の図のように、2 つの効果を別々に、または組み合わせて使用できます。

背景ぼかしのみ

a

背後ぼかしのみ

b

背後ぼかしと背景ぼかし

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. 角丸のウィンドウについては、角丸シェイプ ドローアブルをウィンドウ背景ドローアブルとして設定して、ぼかし領域の角丸を決定します。

  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 を使用して、ウィンドウぼかしが現在有効かどうかを調べます。

  • ウィンドウ背景の 2 つのバージョンを実装し、ウィンドウぼかしの有効状態または無効状態に対応します。

    ぼかしを有効にする場合、ウィンドウ背景を半透明にしてぼかしが表示されるようにする必要があります。この状態でぼかしを無効にすると、ウィンドウのコンテンツが下にあるウィンドウのコンテンツと直接重なり、重なったウィンドウが読みにくくなります。これを防ぐには、ウィンドウぼかしを無効にするときにアプリの UI を次のように調整します。

    • 背景ぼかしの場合は、ウィンドウ背景ドローアブルのアルファを上げ、より不透明にします。

    • 背後ぼかしの場合には、暗さの度合いが高い、暗いレイヤを追加します。

背後ぼかしと背景ぼかしの例

このセクションでは、背後ぼかしと背景ぼかしの両方を使用するアクティビティの実際の例を説明します。

次の MainActivity.java の例は、背後ぼかしの半径を 20 ピクセル、背景ぼかしの半径を 80 ピクセルにしたダイアログです。角丸で、ウィンドウ背景ドローアブルの xml で定義されます。さまざまな Android バージョン、(ウィンドウぼかしをサポートしていない可能性のある)さまざまなデバイス、実行時のぼかしの有効化または無効化を適切に処理します。ウィンドウ背景ドローアブルのアルファとウィンドウの暗さを調整することで、これらのいずれの条件でもダイアログ コンテンツが読み取れるようにします。

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 の角丸シェイプ ドローアブルとして次のように定義します。

<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 にぼかしロジックを実装しています。

デバイスがウィンドウぼかしをサポートできることを確認したら、次のサーフェス フリンガー 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 Emulator でアプリをテストします。Android Emulator をセットアップするには、Android Emulator をセットアップするを参照してください。エミュレータで作成した任意の Android 仮想デバイスは、ウィンドウぼかしをサポートしています。

角が丸くならない

開発者向けオプションを更新してもぼかしが有効にならない

  • デバイスがバッテリー節約モードになっていないか、マルチメディア トンネリングを使用していないかを確認します。一部のテレビデバイスでは、動画再生中にもウィンドウぼかしが無効になることがあります。

背景ぼかしがウィンドウ境界内でなく全画面に描画される

リスナーからの更新が画面に適用されない

  • リスナーの更新が古いウィンドウ インスタンスに適用されている可能性があります。ウィンドウが破棄され、適切なリスナーの更新で再作成されているかを確認します。