개발 공부/문제 해결

android gesture mode 일 때 태스크바 task bar 관련 높이 구하기(sdk30↑) - status bar만 transparent 값 주기

yong_DD 2023. 7. 26. 20:56

*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이상부터 제공하는 WindowInsetsControllerCompatisAppearanceLightStatusBars true/false로 설정하면 색이 변경된다는 것을 발견했다.

 

https://stackoverflow.com/questions/65423778/system-ui-flag-light-status-bar-and-flag-translucent-status-is-deprecated

 

 

[변경된 코드]

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
)

https://debbi.tistory.com/28

 

[Android] 스크린 사이즈 구하기!

https://developer.android.com/reference/android/view/WindowManager#getDefaultDisplay() WindowManager | Android Developers developer.android.com 기존의 WindowManager의 getDefaultDisplay가 API Level 30부터 Deprecated 되었습니다. 가로 fun getScr

debbi.tistory.com

 

그러다가 실제 사이즈를 구하는 코드를 보았고 이 코드로 해봤지만 소용이 없었는데

뭔가 좀 바꿔보면 되지 않을까 해서 파기 시작했다.

 

GetInsetsIgnoringVisibility 부분이 신경 쓰여서 찾아보았다.

대략적으로 이해하기에, 작성한 타입에 따른 화면의 Visibility 상태에 따라 값을 전달해 주는 것 같다.

https://developer.android.com/reference/android/view/WindowInsets#getInsetsIgnoringVisibility(int)

 

WindowInsets  |  Android Developers

 

developer.android.com

 

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시간가량 걸린 것 같다.

틀린 부분도 많고 아직 이해가 덜 된 부분이 있을 거라서 좀 더 공부하고 이해력을 높인 다음 수정 및 추가를 해야겠다.

 

 끄읕!