창 블러

Android 12에서는 창 블러 효과(예: 배경 블러, 후면 블러)를 구현하는 데 공개 API를 사용할 수 있습니다.

창 블러 또는 교차 창 블러는 특정 창 뒤의 화면을 블러 처리하는 데 사용됩니다. 창 블러 유형에는 두 가지가 있으며 다양한 시각적 효과를 달성하는 데 사용할 수 있습니다.

  • 배경 블러를 사용하면 배경이 블러 처리된 창을 만들어 불투명 유리 효과를 낼 수 있습니다.

  • 후면 블러를 사용하면 (대화상자) 창 후면의 전체 화면을 블러 처리하여 심도 효과를 낼 수 있습니다.

두 효과는 다음 그림과 같이 따로 사용하거나 함께 사용할 수 있습니다.

배경만 블러

a

후면만 블러

b

후면 블러 및 배경 블러

c

그림 1. 배경만 블러 (a), 후면만 블러 (b), 배경 블러 및 후면 블러 (c)

창 블러 기능은 여러 창에 걸쳐 적용됩니다. 즉, 창 뒤에 다른 앱이 있는 경우에도 적용됩니다. 이 효과는 동일한 창 내부의 콘텐츠를 블러 처리하는 블러 렌더링 효과와는 다릅니다. 창 블러는 대화상자와 하단 시트, 기타 플로팅 창에 유용합니다.

구현

앱 개발자

앱 개발자는 블러 효과를 만들려면 블러 반경을 제공해야 합니다. 블러 반경은 블러의 밀도를 조절합니다. 즉, 반경이 클수록 블러의 밀도가 높아집니다. 0픽셀 블러는 블러가 없음을 의미합니다. 후면 블러의 경우 반경이 20px이면 심도 효과가 좋고 배경 블러 반경이 80px이면 불투명 유리 효과가 좋습니다. 150px보다 큰 블러 반경은 성능에 크게 영향을 미치므로 피해야 합니다.

원하는 블러 효과를 달성하고 가독성을 높이려면 반투명 색상 레이어로 보완된 블러 반경 값을 선택합니다.

배경 블러

플로팅 창에 배경 블러를 사용하여 기본 콘텐츠의 블러 처리된 이미지인 창 배경 효과를 만듭니다. 창에 블러 처리된 배경을 추가하려면 다음 단계를 따르세요.

  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를 다음과 같이 조정합니다.

    • 배경 블러의 경우 창 배경 드로어블의 알파 값을 높여 더 불투명하게 만듭니다.

    • 후면 블러의 경우 더 높은 어둡게 하기 값으로 어두운 레이어를 추가합니다.

후면 블러와 배경 블러를 모두 사용하는 예

이 섹션에서는 후면 블러와 배경 블러를 모두 사용하는 활동의 실제 예를 제공합니다.

다음 MainActivity.java 예는 후면 블러 반경이 20px이고 배경 블러 반경이 80px인 대화상자입니다. 창 배경 드로어블의 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의 창 배경을 다음과 같이 반경 20dp의 둥근 모서리가 있는 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에서 블러 로직을 구현합니다.

기기에서 창 블러를 지원할 수 있는지 확인한 후 다음과 같이 노출 영역 플링어 sysprop를 설정합니다.

PRODUCT_VENDOR_PROPERTIES += \
       ro.surface_flinger.supports_background_blur=1

유효성 검사

블러 사용 설정 상태와 사용 중지 상태 간에 전환할 때 앱 창이 적절히 처리되는지 확인하려면 다음 단계를 따르세요.

  1. 블러가 적용된 UI를 엽니다.

  2. 창 블러를 켜거나 끄는 방식으로 창 블러를 사용 설정 또는 사용 중지합니다.

  3. 창 UI가 블러 사용 설정 상태와 사용 중지 상태 간에 예상대로 변경되는지 확인합니다.

창 블러 사용 설정 및 사용 중지

창 UI가 창 블러 효과로 렌더링되는 방식을 테스트하려면 다음 방법 중 하나를 사용하여 블러를 사용 설정하거나 사용 중지하세요.

  • 개발자 옵션에서 다음을 선택합니다.

    설정 -> 시스템 -> 개발자 옵션 -> 하드웨어 가속 렌더링 -> 창 수준 블러 허용

  • 루팅된 기기의 터미널에서 다음을 실행합니다.

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

Android 12 이상 기기에서 창 블러를 지원하는지, 창 블러가 현재 사용 설정되어 있는지 확인하려면 루팅된 기기에서 adb shell wm disable-blur를 실행하세요.

문제 해결

유효성 검사를 진행할 때 아래에 따라 문제 해결을 진행하세요.

블러가 그려지지 않음

  • 블러가 현재 사용 설정되어 있고 하드웨어에서 블러를 지원하는지 확인합니다. 창 블러 사용 설정 및 사용 중지를 참고하세요.

  • 창 배경 색상을 반투명으로 설정했는지 확인합니다. 불투명 창 배경 색상은 블러 처리된 영역을 숨깁니다.

테스트 기기가 창 블러를 지원하지 않음

  • Android 12 에뮬레이터에서 애플리케이션을 테스트합니다. Android Emulator를 설정하려면 Android Emulator 설정을 참고하세요. 에뮬레이터를 사용하여 만드는 모든 Android Virtual Device는 창 블러를 지원합니다.

둥근 모서리가 없음

개발자 옵션을 업데이트해도 블러가 사용 설정되지 않음

  • 기기가 배터리 절약 모드에 있는지 또는 멀티미디어 터널링을 사용 중인지 확인합니다. 일부 TV 기기에서는 창 블러가 동영상 재생 중에 사용 중지될 수도 있습니다.

배경 블러가 창 경계 내부에 그려지지 않고 전체 화면에 그려짐

리스너의 업데이트가 화면에 적용되지 않음

  • 리스너 업데이트가 이전 창 인스턴스에 적용되고 있을 수 있습니다. 올바른 리스너 업데이트로 창이 소멸되고 다시 생성되는지 확인합니다.