*22년 11월 기준 작성
구현해야하는 뷰의 조건은 총 5가지였다.
1. 상단의 status bar는 투명이여야 한다. (풀스크린)
2. 버튼은 네비게이션 위에 있어야한다.
3. 스크롤이 맨 상단 일 경우 상태바의 텍스트가 흰색이 되어야 한다.
4. 내용이 스크롤 되면 상태바의 텍스트는 검은색이 되어야 한다.
[처음에 작성했던 코드]
if(!black){
val decorView = window.decorView
if(Build.VERSION.SDK_INT>=30) {
window.setDecorFitsSystemWindows(false)
}else{
decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
}
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv())
}else{
val decorView = window.decorView
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR)
}
상태바가 블랙일 경우(스크롤 될 경우)와 아닌 경우를 나눠서 적용을 했다.
스크롤 될 경우는 status bar가 있는 뒷 배경이 흰색으로 변하도록 했기 때문에 그 부분은 고려 하지 않았고, 맨처음으로 보이는 뷰는 상태바 텍스트가 흰색이기 때문에 이 부분은 고려하지 않았다.
풀스크린을 하는 다른 화면에서 코드를 그대로 가져오다보니 문제점이 많았다.
문제점
1) 내용이 네비게이션 영역까지 침범했다.
2) 버튼이 네비게이션에 가려서 보이지 않았다.
3) 상태바 텍스트가 흰색으로 나와야 할 경우의 코드가 deprecated 되었다.
우선 3번을 해결하기 위해 찾아보았고, 30이상부터 제공하는 WindowInsetsControllerCompat의 isAppearanceLightStatusBars 값 true/false로 설정하면 색이 변경된다는 것을 발견했다.
[변경된 코드]
val wic = WindowInsetsControllerCompat(window, window.decorView)
wic.isAppearanceLightStatusBars = black
if(Build.VERSION.SDK_INT>=30) {
window.setDecorFitsSystemWindows(false)
}else{
decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
}
1,2의 문제를 해결하기 위해 첫 번째로 생각했던 것은 버튼과 뷰의 높이를 네비게이션 뷰의 높이를 마진 값을 주는 것이다.
[1차 시도]
fun getNavigationVisible(): Boolean {
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return if(resourceId<=0) {
false
} else resources.getDimensionPixelSize(resourceId) > 0
}
네비게이션의 높이를 가져와 마진을 준 결과 잘작동하였다.
그러다 문득 네비게이션이 아니라 제스쳐를 사용하면 달라질텐데..?
그래서 확인에 들어갔다.
테스트폰은 폴드3와 S10+였고 결과는 제스처를 사용할 경우 폴드에서는 오픈했을 경우 태스크 바가 가려지고 s10+에서는 네비게이션 영역이 그대로 남겨져 있어서 동일하게 버튼이 가려져 보였다
찾아보니 s10+는 제스처를 사용해도 그 공간이 남아있고(s10이상의 폰은 그렇지 않다.), 폴드에서 태스크 바를 사용할 경우는 네비게이션이 아니기 때문에 네비게이션의 높이만 가져와서 잘되지 않았다.
[네비게이션 사이즈]
1. 네비게이션 사용했을 경우 : 126
2. 제스처 + 힌트 사용했을 경우 : 39
3. 제스처만 사용했을 경우 : 0
이렇게 오고 있었다.
폴드는 접히면 태스크 모드를 사용할 수 없기 때문에 문제가 없었고, s10이상의 폰도 문제가 없었겠지만, 지금도 사용하고 있는 폰을 포기할 수 없었기 때문에 실제 사이즈를 가져오는 코드를 찾기 시작했다.
[2차 시도]
open fun getNavigationBarSize(context: Context): Point? {
val appUsableSize: Point = getAppUsableScreenSize(context)
val realScreenSize: Point = getRealScreenSize(context)
// navigation bar on the side
if (appUsableSize.x < realScreenSize.x) {
return Point(realScreenSize.x - appUsableSize.x, appUsableSize.y)
}
Log.d("bottomSize","real ${realScreenSize.y}, usable ${appUsableSize.y}")
// navigation bar at the bottom
return if (appUsableSize.y < realScreenSize.y) {
Point(appUsableSize.x, realScreenSize.y - appUsableSize.y)
} else Point()
// navigation bar is not present
}
open fun getAppUsableScreenSize(context: Context): Point {
val windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager
val display = windowManager.defaultDisplay
val size = Point()
display.getSize(size)
return size
}
open fun getRealScreenSize(context: Context): Point {
val windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager
val display = windowManager.defaultDisplay
val size = Point()
display.getRealSize(size)
return size
}
https://stackoverflow.com/questions/36514167/how-to-really-get-the-navigation-bar-height-in-android
실제 사이즈를 가져와서 계산하는 코드였는데 이 역시 잘되지 않았다.
여러모로 계산을 해봤으나 태스크바의 사이즈를 가져오지 못했기 때문이다.
태스크 바의 높이를 가져오면 될 것 같아서 여러모로 시도를 해봤으나 전부 실패하였다.
태스크 바의 높이를 가져오는 방법을 찾지 못했다.
구글의 깃의 dimen 값까지 보고 있었다..
그래서 결국 다시 생각해서 상태바만 투명하게 만들 순 없을까 하고 다시 찾다가 코드를 다시 보고 값을 변경을 했다.
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
온종일 잡고 있었는데 한 번에 성공했다.
하지만 부족한 점이 있었다.
이 코드는 deprecated 된 것이었던 것.
if (Build.VERSION.SDK_INT >= 30) {
window.setDecorFitsSystemWindows(false)
} else {
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
30 이상이 더 많기 때문에 결국 window.setDecorFitsSystemWindows(false) 이 코드로 돌아올 수밖에 없었는데, 30 이상은 도저히 찾을 수가 없어서 결국 다시 30 이상은 마진을 주는 방법으로 돌아가서 생각했다.
[3차 시도]
val metrics. = windowManager.currentWindowMetrics
val windowInsets = metrics.windowInsets
val insets: Insets = windowInsets.getInsetsIgnoringVisibility(
WindowInsets.Type.navigationBars()
or WindowInsets.Type.displayCutout()
)
val insetsWidth: Int = insets.right + insets.left
val insetsHeight: Int = insets.top + insets.bottom
val bounds: Rect = metrics.bounds
val legacySize = Size(
bounds.width() - insetsWidth,
bounds.height() - insetsHeight
)
그러다가 실제 사이즈를 구하는 코드를 보았고 이 코드로 해봤지만 소용이 없었는데
뭔가 좀 바꿔보면 되지 않을까 해서 파기 시작했다.
GetInsetsIgnoringVisibility 부분이 신경 쓰여서 찾아보았다.
대략적으로 이해하기에, 작성한 타입에 따른 화면의 Visibility 상태에 따라 값을 전달해 주는 것 같다.
https://developer.android.com/reference/android/view/WindowInsets#getInsetsIgnoringVisibility(int)
windowManager.currentWindowMetrics 와 windowManager.maximumWindowMetrics 의 값은 동일한 것을 확인했고 GetInsetsIgnoringVisibility 의 타입 값만 변경을 하면서 체크를 해보았다.
그러던 도중 WindowInsets.Type.navigationBars() 이 타입만 넣었을 경우 정상적으로 값이 오는 것을 확인했다!!
val inset = windowManager.currentWindowMetrics.windowInsets.getInsetsIgnoringVisibility(
WindowInsets.Type.navigationBars()
)
inset.bottom 값을 margin으로 넣으면 정확히 작동했다!
폴드의 경우 접었다 폈을 때 반복하면 값을 또 못 가져오는 경우가 생겨서 메인 쓰레드로 돌리는 코드를 추가하여 최종 코드는 이렇다.
[최종 코드]
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= 30) {
window.setDecorFitsSystemWindows(false)
} else {
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
}
override fun onTopResumedActivityChanged(isTopResumedActivity: Boolean) {
super.onTopResumedActivityChanged(isTopResumedActivity)
if (Build.VERSION.SDK_INT >= 30) {
setBottomMargin()
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun setBottomMargin(){
windowManager.maximumWindowMetrics
CoroutineScope(Dispatchers.Main).launch {
val inset = windowManager.currentWindowMetrics.windowInsets.getInsetsIgnoringVisibility(
WindowInsets.Type.navigationBars())
vm.setButtonMargin(binding.goReservationBtnView,binding.mapView,inset.bottom) - 뷰모델에 전달하는 부분
}
}
private fun statusBarControl(black:Boolean){
val wic = WindowInsetsControllerCompat(window, window.decorView)
wic.isAppearanceLightStatusBars = black
}
Create 될 때 status bar 투명을 만들어 주고, 30이상일 경우에만 풀스크린이기 때문에 (네비게이션 뷰까지 침범) onTopResumedActivityChanged가 호출 될 때마다 버튼과 뷰의 마진을 다시 설정해주고 스크롤 될 때 상태바의 텍스트의 색을 변경되도록 하는 코드를 구성하게 되었다.
이렇게 오기까지 많은 시도 끝에 약 10~12시간가량 걸린 것 같다.
틀린 부분도 많고 아직 이해가 덜 된 부분이 있을 거라서 좀 더 공부하고 이해력을 높인 다음 수정 및 추가를 해야겠다.
끄읕!
'개발 공부 > 문제 해결' 카테고리의 다른 글
[android] ClickableViewAccessibility 해결/2중 레이아웃 터치 문제 (0) | 2021.06.17 |
---|