관리 메뉴

Optimal Solution

Activity 생성 시 Code Snippet이 달라졌다 ... ! (1) 본문

Android

Activity 생성 시 Code Snippet이 달라졌다 ... ! (1)

ICE_MAN 2024. 4. 27. 23:25
728x90

2023년 11월에 회사에 입사하고나서, Android 개발을 맡지 않게 되어 따로 Android 개발을 건드릴 일이 없었다.

최근 간만에 감 좀 살려보고자 Android Studio를 켜서 프로젝트를 하나 생성했는데 기본으로 생성되는 Activity 코드를 보고 눈을 싹싹 비볐다.

 

이 ... 이게 뭐농 ...

분명 예전에는 이렇게까지 현란하게 코드 스니펫이 있던 것 같지는 않은데 ... 내 기억 속의 코드는 이랬다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

 

새롭게 추가된 것은 enableEdgeToEdge()와 setOnApplyWindowInsetsListener()인데 이게 뭐 하는 놈인가 싶어서 좀 찾아보고 포스트를 작성하고자 한다.

이번 포스트에서는 enableEdgeToEdge()만 우선 살펴보겠다.

 

공식 문서와 소스코드를 참고해 알아보도록 하자.

 

edge-to-edge 관련 공식문서 링크 : https://developer.android.com/develop/ui/views/layout/edge-to-edge

 

 

enableEdgeToEdge()

enableEdgeToEdge() 메서드

enableEdgeToEdge()는 ComponentActivity의 확장함수이다.

 

메서드 설명

이 ComponentActivity가 edge-to-edge 디스플레이를 활성화한다.

디폴트 스타일로 설정하기 위해서 Activity의 onCreate 메서드 안에서 이 메서드를 호출하면 된다.

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

 

디폴트 스타일은 시스템에 의해 대비가 (강제로) 적용될 수 있을 때(API 29 이상0, 투명한 배경으로 System Bar를 구성한다.

이전 플랫폼(3버튼 혹은 2버튼 navigation mode만 있는 경우)에서는 System Bar와의 대비를 보장하기 위해 동등한 scrim이 적용된다.

 

자세한 커스텀 옵션은 SystemBarStyle을 참고한다.

 

파라미터

파라미터는 statusBarStyle과 navigationBarStyle이라는 2개의 SystemBarStyle 객체를 받는다. 각각 Status Bar/Navigation Bar에 대한 Style을 의미하는데, SystemBarStyle에 대해서는 아래에서 자세히 알아보겠다.

별도의 SystemBarStyle 객체를 넘겨받지 않는다면, 디폴트로 statusBarStyle은 투명으로 지정되고 navigationBarStyle은 기본 라이트/다크 scrim으로 지정된다.

- 기본 라이트 scrim : 알파값 0xE6, RED 0xFF, GREEN 0xFF, BLUE 0xFF

- 기본 다크 scrim : 알파값 0x80, RED 0x1B, GREEN 0x1B, BLUE 0x1B

 

메서드 내부 구현

메서드 내부 구현을 알아보기에 앞서, 기본적인 Android 화면의 구조를 알고 넘어가보도록 하자.

 

Window

Window는 콘텐츠와 사용자 상호작용을 표시하는 데 사용되는 추상 Base 클래스이다.

Window는 모든 뷰가 그려지는 투명한 사각형이라고 할 수 있다.

 

Activity를 생성할 때, ActivityThread에 Activity를 첨부해야 한다.

attach()는 onCreate 전에 호출되며, attach()에서는 Activity에 대한 Window가 생성된다.

 

@UnsupportedAppUsage
final void attach(Context context, ... ) {
	// ...
	mWindow = new PhoneWindow(this, window, activityConfigCallback);
	// ...
}

 

 

PhoneWindow

PhoneWindow는 Activity에서 제공하는 Window로, View 계층 단계 이전의 root이다.

PhoneWindow에는 두 가지 중요한 파라미터인 DecorView와 ViewGroup이 있다.

 

DecorView는 PhoneWindow의 내부 클래스이며 Activity의 뷰 계층 구조에서 루트 컨테이너에 해당한다.

DecorView는 FrameLayout을 확장한다.

DecorView에는 그릴 수 있는 Window 배경이 포함되어 있으며, 예를 들어 getWindow().setBackgroundDrawable()을 호출하면 Window의 배경이 변경된다.

 

ViewGroup은 창의 콘텐츠가 배치되는 뷰를 저장한다.

뷰 계층 구조

이러한 구조를 이해한 상태로, 메서드의 동작을 이해해보자.

 

1. 화면 정보 가져오기

val view = window.decorView
val statusBarIsDark = statusBarStyle.detectDarkMode(view.resources)
val navigationBarIsDark = navigationBarStyle.detectDarkMode(view.resources)

 

Window의 DecorView를 가져와서, statusBarStyle과 navigationBarStyle가 다크 모드인지를 DecorView의 리소스를 통해 체크한다.

 

2. EdgeToEdgeImpl 인터페이스의 setUp() 호출

val impl = Impl ?: if (Build.VERSION.SDK_INT >= 29) {
    EdgeToEdgeApi29()
} else if (Build.VERSION.SDK_INT >= 26) {
    EdgeToEdgeApi26()
} else if (Build.VERSION.SDK_INT >= 23) {
    EdgeToEdgeApi23()
} else if (Build.VERSION.SDK_INT >= 21) {
    EdgeToEdgeApi21()
} else {
    EdgeToEdgeBase()
}.also { Impl = it }
impl.setUp(
    statusBarStyle, navigationBarStyle, window, view, statusBarIsDark, navigationBarIsDark
)

EdgeToEdge~()를 통해 각각 API의 버전 범위에 맞는 EdgeToEdgeImpl을 impl 변수에 할당한다.

할당 후 EdgeToEdgeImpl 객체의 setUp을 호출하는데, 다양한 버전의 setUp() 중 API 29 이상 버전만 보도록 하겠다. 더 궁금하면 직접 까서 봐라.

 

@RequiresApi(29)
private class EdgeToEdgeApi29 : EdgeToEdgeImpl {

    @DoNotInline
    override fun setUp(
        statusBarStyle: SystemBarStyle,
        navigationBarStyle: SystemBarStyle,
        window: Window,
        view: View,
        statusBarIsDark: Boolean,
        navigationBarIsDark: Boolean
    ) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        window.statusBarColor = statusBarStyle.getScrimWithEnforcedContrast(statusBarIsDark)
        window.navigationBarColor =
            navigationBarStyle.getScrimWithEnforcedContrast(navigationBarIsDark)
        window.isStatusBarContrastEnforced = false
        window.isNavigationBarContrastEnforced =
            navigationBarStyle.nightMode == UiModeManager.MODE_NIGHT_AUTO
        WindowInsetsControllerCompat(window, view).run {
            isAppearanceLightStatusBars = !statusBarIsDark
            isAppearanceLightNavigationBars = !navigationBarIsDark
        }
    }
}

WindowCompat.setDecorFitsSystemWindow()는 root view인 DecorView를 Full-View로 채워준다.

window.~BarColor는, Window의 다크 모드인지 여부를 참고해 Status Bar, Navigation Bar 색상을 지정한 색상으로 바꾸는 코드이다.

window.is~BarContrastEnforced는 완전히 투명한 배경이 요청될 때 시스템에서 System Bar에 대해 충분한 대비를 적용할지의 여부를 설정하는 코드이다. Status Bar는 false를, NavigationBar는 다크 모드이면 true를 적용함을 알 수 있다.

마지막으로 WindowInsetsControllerCompat을 통해 isAppearanceLight~Bars를 설정한다. 이 값이 true이면 System Bar의 전경(foreground) 색을 밝게 변경해 항목을 명확히 읽을 수 있도록 하고 false이면 기본 모양으로 되돌린다.

 

enableEdgeToEdge() 적용 결과

(가사는 내가 좋아하는 Oasis의 Don't look back in anger이다 ^^)

enableEdgeToEdge()만 적용하면 아래와 같이 Status Bar 영역까지도 사용할 수 있음을 알 수 있다.

enableEdgeToEdge()만 적용한 결과(신기하눙 우하하)

 

마치며

edgeToEdge()에 대해서 알아보려다가 너무 많이 알아본 것 같기도 한데 ...

기본적인 화면 구성 원리에 대해서 몰라서 좀 이해하는 데 걸린 것 같다.

그만큼 내가 멍청한 것 같다.

 

안드로이드를 너무 오래 놨나 ...... 회사에서 다른 일을 하니까 뇌가 녹고있다.

728x90