-
안녕하세요, 하루플입니다.
최근 회사 프로젝트를 진행하면서 Android 15 버전을 대응하게 되었는데 간단(?)할 줄 알았으나 생각보다 시간을 써서 문제 해결 과정을 적어봅니다.
Android15 부터 더 넓은 화면을 표시하도록 하는 edge to edge 함수가 모든 화면에 적용되고, 구글에서도 더 넓은 화면으로 개발하기를 권장하고 있습니다.
상단 StatusBar 영역과, 하단 Navigation Bar 이 투명하게 확장되었습니다.
이로인해 기존 앱 UI에 문제가 생겼습니다.
Android 14 / Android 15 Android 15에서는 StatusBar 부분이 투명해지면서 UI가 전체적으로 올라가게 되었고, 뒤로가기 버튼 등 여러 UI가 StatusBar, NavigationBar와 겹쳐지게 되었습니다.
위 문제를 구글에서는 Inset 정보를 가져와서 margin이나 padding을 적용해서 해결하도록 권장하고 있습니다.
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> val statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top val navBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom insets }
저희 프로젝트는 대부분 Activity 하나에 여러개의 Fragment로 이루어져있고, 일부 Compose로 전환된 상태입니다.
위 코드를 Activity와 Fragment에서 사용했을 때 아래의 2가지 문제가 발생했습니다.
1. Fragment 에서 setOnApplyWindowInsetsListener 의 Inset 값이 호출되지 않는 문제 발생.
2. 여러화면에서 동시에 setOnApplyWindowInsetsListener 호출시 가장 마지막에 호출된 리스너만 등록되는 문제 발생.사실 더 쉬운 방법으로 statusBar 와 navigationBar height를 가져오는 방법을 알고 있었지만, 앞으로의 구글 업데이트에 요긴하게 대처하고 구글이 권장하는 방법을 따르기 위해 Inset 을 사용하는 방법으로 계속 개발을 진행했습니다.
1. StatusBar, NavBar Height 구하는 유틸리티 함수
/** * Android 15 statusBar, navBar Deprecated * 이를 대비하여 최상단, 최하단 뷰에 마진을 추가하기 휘해 높이를 구하는 함수 입니다. */ fun onSystemBarInsetsChanged( view: View, onChanged: (statusBarHeight: Int, navBarHeight: Int) -> Unit ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> val statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top val navBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom onChanged(statusBarHeight, navBarHeight) insets } } else { onChanged(0, 0) } }
유틸리티 클래스에 statusBar와 navBar Height를 구하는 함수를 먼저 만들었습니다.
리스너에서 Inset의 변화를 감지하면 람다함수로 전달하도록 했습니다.
2. 뷰에 마진을 더하는 유틸리티 함수 개발
private val baseTopMargins = mutableMapOf<View, Int>() // 함수를 여러번 실행해도 높이가 중복으로 증가하지 않도록 첫 마진값 저장 fun applyStatusBarMargin(height:Int, views: List<View>) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { views.forEach { view -> val baseMargin = baseTopMargins.getOrPut(view) { view.marginTop } view.updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin = baseMargin + height } } } } private val baseBottomMargins = mutableMapOf<View, Int>() // 함수를 여러번 실행해도 높이가 중복으로 증가하지 않도록 첫 마진값 저장 fun applyNavigationBarMargin(height: Int, views: List<View>) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { views.forEach { view -> val baseMargin = baseBottomMargins.getOrPut(view) { view.marginBottom } view.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = baseMargin + height } } } }
함수를 여러번 실행했을 때 View의 마진이 중복으로 늘어나지 않도록 baseMargins 라는 전역변수를 만들어두었습니다.
3. viewModel 에 값 저장
viewModel 에 아래와 같이 height를 저장하는 변수를 선언합니다.
val statusBarHeight = MutableLiveData<Int>().apply { value = 0 } val navBarHeight = MutableLiveData<Int>().apply { value = 0 }
Activity 에 아래와 같이 height 정보를 ViewModel에 저장하도록 합니다.
onSystemBarInsetsChanged(binding.root) { statusBarHeight, navBarHeight -> addProfileVM.statusBarHeight.value = statusBarHeight addProfileVM.navBarHeight.value = navBarHeight }
4. observe로 margin 업데이트
addProfileVM.statusBarHeight.observe(viewLifecycleOwner) { applyStatusBarMargin(height = it, views = listOf(binding.ivBack)) } addProfileVM.navBarHeight.observe(viewLifecycleOwner) { applyNavigationBarMargin(height = it, views = listOf(binding.btnNext)) }
아까 만들어둔 마진 업데이트 유틸리티 함수를 활용해 뷰를 업데이트 합니다.
NavigationBar에 가려진 RecyclerView RecyclerView의 경우 항목의 최하단에 padding이 추가되어야 Navigation Bar에 가려지지 않으므로 아래 코드를 작성해줍니다.
binding.recyclerView.setPadding( binding.recyclerView.paddingLeft, binding.recyclerView.paddingTop, binding.recyclerView.paddingRight, navBarHeight // 하단 padding 추가 ) binding.recyclerView.clipToPadding = false // padding 영역까지 스크롤되도록 설정
이렇게 Activity에서만 Inset을 얻어오고, 실제 뷰가 존재하는 Fragment에서 뷰를 업데이트 하는 간단하고 짧은 코드로 모든 화면을 Android 15에 대응하게 되었습니다.
'개발 > Android' 카테고리의 다른 글
[Android | Kotlin] 테두리가 있는 TextView 개발하기 (1) 2023.06.07 [Android | Kotlin] 프로그래스바 커스텀 하기 (0) 2023.06.06 [Android | Kotlin] Uri로 입력받은 이미지를 줄이는 방법 (0) 2023.06.04 심플 소프트웨어 : 소프트웨어 이해하기 (1) 2023.05.29 심플 소프트웨어 : 엔지니어링 팀에서 일하기 (0) 2023.05.28 댓글