스플래시 화면

Android 12는 Android 12 이상을 실행하는 기기에서 실행할 때 모든 앱에 새로운 앱 시작 애니메이션을 사용 설정하는 SplashScreen API를 추가합니다. 여기에는 실행 시 앱 내 모션, 앱 아이콘을 보여주는 스플래시 화면, 앱 자체로의 전환이 포함됩니다.

스플래시 화면의 예
그림 1: 스플래시 화면의 예

새로운 환경은 앱을 출시할 때마다 표준 디자인 요소를 제공하지만 맞춤설정도 가능하므로 앱이 고유한 브랜딩을 유지할 수 있습니다.

SplashScreen API를 직접 사용하는 것 외에도 SplashScreen API를 래핑하는 SplashScreen compat 라이브러리를 사용하는 방법도 있습니다.

스플래시 화면 작동 방식

사용자가 앱을 실행할 때 앱 프로세스가 실행되지 않거나(콜드 스타트) 활동이 만들어지지 않은 상태(웜 스타트)라면 다음 이벤트가 발생합니다. 스플래시 화면은 핫 스타트 중에 표시되지 않습니다.

  1. 시스템은 개발자가 정의한 테마와 애니메이션을 사용하여 스플래시 화면을 표시합니다.

  2. 앱이 준비되면 스플래시 화면이 닫히고 앱이 표시됩니다.

애니메이션의 요소와 메커니즘

애니메이션 요소는 Android 매니페스트의 XML 리소스 파일로 정의됩니다. 각각 밝은 모드와 어두운 모드 버전이 있습니다.

창 배경과 애니메이션 앱 아이콘, 아이콘 배경으로 구성됩니다.

스플래시 화면의 요소
그림 2: 맞춤설정 가능한 스플래시 화면 요소

이러한 요소와 관련하여 다음 고려사항에 유의하세요.

  • 앱 아이콘(1)은 벡터 드로어블이어야 하고 정적이거나 애니메이션일 수 있습니다. 애니메이션의 지속 시간은 무제한일 수 있지만 1,000밀리초를 초과하지 않는 것이 좋습니다. 기본적으로 런처 아이콘이 사용됩니다.

  • 아이콘 배경(2)은 선택사항이고 아이콘과 창 배경 사이에 대비가 더 필요한 경우 유용합니다. 적응형 아이콘을 사용하면 창 배경과 대비가 충분한 경우 배경이 표시됩니다.

  • 적응형 아이콘과 마찬가지로 전경의 1/3이 마스크 처리(3)됩니다.

  • 창 배경(4)은 단일 불투명 색상으로 구성됩니다. 창 배경이 설정되어 있고 단색인 경우 속성이 설정되어 있지 않으면 기본적으로 사용됩니다.

스플래시 화면 애니메이션 메커니즘은 들어가기나가기 애니메이션으로 구성됩니다.

  • 들어가기 애니메이션은 스플래시 화면의 시스템 뷰로 구성됩니다. 시스템에서 제어하고 맞춤설정할 수 없습니다.

  • 나가기 애니메이션은 스플래시 화면을 숨기는 애니메이션 실행으로 구성됩니다. 맞춤설정하려는 경우 SplashScreenView 및 아이콘에 액세스한 후 거기에서 변환, 불투명도, 색상 설정을 사용하여 어떤 애니메이션이든 실행할 수 있습니다. 이 경우 애니메이션이 완료될 때 스플래시 화면을 수동으로 삭제해야 합니다.

앱의 스플래시 화면 맞춤설정

기본적으로 SplashScreen은 단색에 런처 아이콘이면 테마의 windowBackground를 사용합니다. 스플래시 화면의 맞춤설정은 앱 테마에 속성을 추가하면 됩니다.

앱의 스플래시 화면은 다음과 같은 방법으로 맞춤설정할 수 있습니다.

  • 테마 속성을 설정하여 모양 변경

  • 화면에 더 오래 표시

  • 스플래시 화면 닫기 애니메이션 맞춤설정

스플래시 화면 테마를 설정하여 모양 변경

활동 테마에서 다음 속성을 지정하여 앱의 스플래시 화면을 맞춤설정할 수 있습니다. android:windowBackground와 같은 속성을 사용하는 기존 스플래시 화면 구현이 이미 있다면 Android 12 이상의 대체 리소스 파일을 제공하는 것이 좋습니다.

  1. windowSplashScreenBackground를 사용하여 특정 단색으로 배경을 채웁니다.

    <item name="android:windowSplashScreenBackground">@color/...</item>
    
  2. windowSplashScreenAnimatedIcon을 사용하여 시작 창 중앙의 아이콘을 대체합니다. 객체가 AnimationDrawableAnimatedVectorDrawable을 통해 애니메이션 가능하고 드로어블이라면 시작 창을 표시하는 동안 애니메이션이 재생되도록 windowSplashScreenAnimationDuration을 설정할 수도 있습니다.

    <item name="android:windowSplashScreenAnimatedIcon">@drawable/...</item>
    
  3. windowSplashScreenAnimationDuration을 사용하여 스플래시 화면 아이콘 애니메이션의 지속 시간을 나타냅니다. 이 설정을 사용해도 스플래시 화면이 표시되는 실제 시간은 영향을 받지 않지만, SplashScreenView#getIconAnimationDuration을 사용하여 스플래시 화면 종료 애니메이션을 맞춤설정할 때 지속 시간을 가져올 수 있습니다. 자세한 내용은 다음 섹션의 스플래시 화면을 화면에 더 오래 표시를 참고하세요.

    <item name="android:windowSplashScreenAnimationDuration">1000</item>
    
  4. windowSplashScreenIconBackgroundColor를 사용하여 스플래시 화면 아이콘 뒤의 배경을 설정합니다. 창 배경과 아이콘 사이에 대비가 충분하지 않은 경우에 유용합니다.

    <item name="android:windowSplashScreenIconBackgroundColor">@color/...</item>
    
  5. 원하는 경우 windowSplashScreenBrandingImage를 사용하여 스플래시 화면 하단에 표시할 이미지를 설정할 수 있습니다. 디자인 가이드라인에서는 브랜드 이미지 사용을 권장하지 않습니다.

    <item name="android:windowSplashScreenBrandingImage">@drawable/...</item>
    

스플래시 화면을 화면에 더 오래 표시

스플래시 화면은 앱이 첫 프레임을 그리는 즉시 닫힙니다. 로컬 디스크에서 인앱 설정과 같은 소량의 데이터를 비동기적으로 로드해야 한다면 ViewTreeObserver.OnPreDrawListener를 사용하여 첫 프레임을 그릴 앱을 정지할 수 있습니다.

시작 활동이 그리기 전에 완료되는 경우(예: 콘텐츠 뷰를 설정하지 않고 onResume 전에 종료하는 경우) 사전 그리기 리스너가 필요하지 않습니다.

Kotlin

// Create a new event for the activity.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Set the layout for the content view.
    setContentView(R.layout.main_activity)

    // Set up an OnPreDrawListener to the root view.
    val content: View = findViewById(android.R.id.content)
    content.viewTreeObserver.addOnPreDrawListener(
        object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
                // Check if the initial data is ready.
                return if (viewModel.isReady) {
                    // The content is ready; start drawing.
                    content.viewTreeObserver.removeOnPreDrawListener(this)
                    true
                } else {
                    // The content is not ready; suspend.
                    false
                }
            }
        }
    )
}

자바

// Create a new event for the activity.
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the layout for the content view.
    setContentView(R.layout.main_activity);

    // Set up an OnPreDrawListener to the root view.
    final View content = findViewById(android.R.id.content);
    content.getViewTreeObserver().addOnPreDrawListener(
            new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    // Check if the initial data is ready.
                    if (mViewModel.isReady()) {
                        // The content is ready; start drawing.
                        content.getViewTreeObserver().removeOnPreDrawListener(this);
                        return true;
                    } else {
                        // The content is not ready; suspend.
                        return false;
                    }
                }
            });
}

스플래시 화면 닫기 애니메이션 맞춤설정

Activity.getSplashScreen()을 통해 스플래시 화면의 애니메이션을 추가로 맞춤설정할 수 있습니다.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ...

    // Add a callback that's called when the splash screen is animating to
    // the app content.
    splashScreen.setOnExitAnimationListener { splashScreenView ->
        // Create your custom animation.
        val slideUp = ObjectAnimator.ofFloat(
            splashScreenView,
            View.TRANSLATION_Y,
            0f,
            -splashScreenView.height.toFloat()
        )
        slideUp.interpolator = AnticipateInterpolator()
        slideUp.duration = 200L

        // Call SplashScreenView.remove at the end of your custom animation.
        slideUp.doOnEnd { splashScreenView.remove() }

        // Run your animation.
        slideUp.start()
    }
}

자바

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // ...

    // Add a callback that's called when the splash screen is animating to
    // the app content.
    getSplashScreen().setOnExitAnimationListener(splashScreenView -> {
        final ObjectAnimator slideUp = ObjectAnimator.ofFloat(
                splashScreenView,
                View.TRANSLATION_Y,
                0f,
                -splashScreenView.getHeight()
        );
        slideUp.setInterpolator(new AnticipateInterpolator());
        slideUp.setDuration(200L);

        // Call SplashScreenView.remove at the end of your custom animation.
        slideUp.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                splashScreenView.remove();
            }
        });

        // Run your animation.
        slideUp.start();
    });
}

이 콜백이 시작되면 스플래시 화면의 애니메이션 벡터 드로어블이 시작됩니다. 앱 실행 시간에 따라 드로어블은 애니메이션 중간에 있을 수 있습니다. SplashScreenView.getIconAnimationStart를 사용하여 애니메이션이 시작된 시점을 알 수 있습니다. 다음과 같이 아이콘 애니메이션의 남은 시간을 계산할 수 있습니다.

Kotlin

// Get the duration of the animated vector drawable.
val animationDuration = splashScreenView.iconAnimationDuration
// Get the start time of the animation.
val animationStart = splashScreenView.iconAnimationStart
// Calculate the remaining duration of the animation.
val remainingDuration = if (animationDuration != null && animationStart != null) {
    (animationDuration - Duration.between(animationStart, Instant.now()))
        .toMillis()
        .coerceAtLeast(0L)
} else {
    0L
}

자바

// Get the duration of the animated vector drawable.
Duration animationDuration = splashScreenView.getIconAnimationDuration();
// Get the start time of the animation.
Instant animationStart = splashScreenView.getIconAnimationStart();
// Calculate the remaining duration of the animation.
long remainingDuration;
if (animationDuration != null && animationStart != null) {
    remainingDuration = animationDuration.minus(
            Duration.between(animationStart, Instant.now())
    ).toMillis();
    remainingDuration = Math.max(remainingDuration, 0L);
} else {
    remainingDuration = 0L;
}