천복만복 프로그래밍/천복만복 안드로이드

Edge to Edge

U&MeBlue 2024. 10. 2. 23:27

https://developer.android.com/develop/ui/views/layout/edge-to-edge

 

앱에 더 넓은 화면에 콘텐츠 표시  |  Views  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱에 더 넓은 화면에 콘텐츠 표시 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose 방식 사용해

developer.android.com

 

기본 개념

기본적으로 앱이 그려질 영역은 상태 바나 네비게이션 바와 같은 시스템 UI 와 겹쳐지지 않도록 크기가 조절되어 있다.

class MainActivity : AppCompatActivity() {  

    private lateinit var binding: ActivityMainBinding  

    override fun onCreate(savedInstanceState: Bundle?) {  
       super.onCreate(savedInstanceState)  
       binding = ActivityMainBinding.inflate(layoutInflater)  
       setContentView(binding.root)  
    }  
}

 

 

 

activty ktx 의존성에 포함된 확장 함수를 이용하면 앱의 컨텐츠를 화면의 상단 끝에서 하단 끝까지 모두 이용하여 표시할 수 있다. 이렇게 되는 경우 앱의 컨텐츠와 시스템 UI 가 겹쳐져서 나타난다.

 

dependencies {
    // 의존성 추가
    val activity_version = `activity_version`
    // Java language implementation
    implementation("androidx.activity:activity:$activity_version")
    // Kotlin
    implementation("androidx.activity:activity-ktx:$activity_version")
}
class MainActivity : AppCompatActivity() {  

    private lateinit var binding: ActivityMainBinding  

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

       binding = ActivityMainBinding.inflate(layoutInflater)  
       setContentView(binding.root)  
    }  
}

 

앱의 UI 가 디스플레이의 끝에서 끝 (Edge to Edge) 까지 모두 표시된다. 이때 겹쳐진 시스템 UI 가 앱의 컨텐츠를 가리지 않도록 enableEdgeToEdge() 확장함수 내부에서 자동으로 시스템 UI 를 투명 또는 반투명하게 처리해준다.

 

이렇게 앱의 Content 를 디스플레이의 끝에서 끝까지 모두 표시하는 것을 안드로이드 진영에서는 Edge To Edge 라는 용어로 많이 표현하는 것 같다.

 

Android 15 (SDK 35) 미만의 버전에서는 enableEdgeToEdge() 확장함수를 사용해야 Edge To Edge 가 적용되지만 Target SDK 를 Android 15 (SDK 35) 로 올리는 경우에는 자동으로 Edge To Edge 가 적용된다. 즉, 아무런 처리를 하지 않아도 앱의 컨텐츠가 디스플레이의 끝에서 끝까지 모두 사용하여 표현된다.

Handle overlaps using Insets

Edge To Edge 를 활성화 시키는 경우 아래 그림처럼 앱의 컨텐츠가 표시될 수 있다.

 

 

위 예시에서 문제가 될 수 있는 부분은 Floating Action Button(FAB) 이다. FAB 의 일부 영역이 네비게이션 바에 의해서 가려져 있어 온전한 터치 영역을 확보할 수 없는 상태이다.

 

이렇듯 Edge To Edge 를 활성화 하는 경우 앱의 컨텐츠가 다른 시스템 UI 에 의해서 의도하지 않은 형태로 가려지는 것을 방지해야 한다. 이를 위해서 Inset 을 활용할 수 있다.

 

Inset 은 스크린에서 어떤 부분이 시스템 UI 에 의해 가려지고 있는지를 나타내는 정보이다. 다음과 같은 Inset 이 있다.

  • System UI Inset : 상태 바나 네비게이션 바와 같은 시스템 UI 에 의해서 가려지는 영역의 정보를 나타낸다.
  • Display Cutout Inset : 몇몇 안드로이드 디바이스에는 아이폰의 노치와 같은 유사한 형태의 디스플레이를 가지고 있는 경우가 있다. 이런 디스플레이 모양에 의해서 가려지는 영역의 정보를 나타낸다.
  • System Gesture Inset : 제스쳐 뒤로가기와 같이 앱보다 높은 우선순위로 시스템이 인터렉션을 처리하는 영역의 정보를 나타낸다.

System bars insets 처리

앱의 UI 에서 FAB 과 같이 인터렉션이 필요한 부분이나 가려지는 부분 없이 온전히 화면에 표시되어야 하는 것들은 시스템 UI 에 방해를 받아서는 안된다. 이를 방지하기 위해 System bars Inset 정보를 이용해서 View 의 위치나 마진, 패딩 값을 조절해줄 수 있다.

방금 전 FAB 이미지 문제를 수정하기 위해 다음과 같이 처리해줄 수 있다.

class MainActivity : AppCompatActivity() {  

    private lateinit var binding: ActivityMainBinding  

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

       binding = ActivityMainBinding.inflate(layoutInflater)  
       setContentView(binding.root)

       // 이처럼 System bar Inset 정보를 불러와서 FAB 에 마진을 조절해준다.
       ViewCompat.setOnApplyWindowInsetsListener(binding.fab) { v, windowInsets ->  
          val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
             // inset 정보를 불러와서 FAB 의 마진 값을 조절해준다.
             leftMargin = insets.left  
             bottomMargin = insets.bottom  
             rightMargin = insets.right  
          }  

          // 하위 뷰에 Inset 정보를 넘겨줄 지 말지 결정할 수 있는데,
          // WindowInsetsCompat.CONSUMED 를 반환하면 Inset 정보를 
          // 하위 뷰에 넘겨주지 않겠다는 의미이다.        
          WindowInsetsCompat.CONSUMED  
       }  
    }  
}

 

 

Display Cutout insets 처리

몇몇 안드로이드 단말의 디스플레이는 아이폰의 노치와 같이 일부 영역이 센서나 카메라에 의해서 가려지게 된다. 이렇게 센서나 카메라에 의해 가려지는 영역을 안드로이드 진영에서는 Display Cutout 이라고 한다. 그리고 Display Cutout 에 의해 가려지는 영역의 정보를 Display Cutout Inset 이라고 한다.

 

 

Portrait 모드일 경우 이러한 Display Cutout inset 은 대부분 System bar inset 영역, 정확히는 상태 바 내부에 포함될 가능성이 많다. 따라서 System bar inset 에 관한 처리만 해주어도 상관이 없을 것이다.

 

문제는 Landscape 모드이다. 이때는 상태 바가 표시될 공간에 Display Cutout 이 포함되지 않게 된다. 따라서 Display Cutout 영역에 앱의 컨텐츠를 보여줘야 할지 말지를 결정할 필요가 있을 수 있다.

 

Landscape 모드에서 Display Cutout 영역에 앱의 컨텐츠 표시

 

Landsacpe 모드에서 Display Cutout (여기서는 전면 카마라 구멍) 에 의해 앱의 컨텐츠가 가려지는게 싫다면 View 의 시작 마진을 조절하거나 패딩을 조절하여 앱의 컨텐츠가 Display Cutout 과 겹치지 않도록 한다.

ViewCompat.setOnApplyWindowInsetsListener(binding.videoPlayer) { v, insets ->
  val bars = insets.getInsets(
    WindowInsetsCompat.Type.systemBars()
      or WindowInsetsCompat.Type.displayCutout()
  )

  v.updatePadding(
    left = bars.left,
    top = bars.top,
    right = bars.right,
    bottom = bars.bottom,
  )

  WindowInsetsCompat.CONSUMED
}

System Gesture insets 처리

System Gesture inset 은 시스템 제스쳐를 처리할 용도로 앱보다 우선하여 시스템에 의해 인터렉션이 처리되는 영역을 말한다. 언제부터인가 안드로이드에서도 아이폰과 유사한 제스처를 이용하여 몇가지 동작을 처리할 수 있게 되었다. 화면의 하단부터 쓸어 올려 홈 화면으로 이동하는 등의 제스처가 그 예다. 홈화면 이동뿐만 아니라 화면의 왼쪽이나 오른쪽부터 쓸어서 좌우로 이동하면 뒤로가기 동작이 수행된다. (개인적으로 예전 버튼 방식이 좋은듯..)

 

 

 

만약 이런 제스처를 처리하는 부분에 앱에서도 뭔가 비슷한 인터렉션이 수행되면 항상 시스템 제스처가 우선순위가 높기 때문에 앱의 인터렉션은 수행되지 않는다. 예를 들어 ViewPager 를 이용해서 좌우로 화면을 넘겨 여러가지 컨텐츠를 보여주는 앱의 경우에 좌우 스와이프와 시스템 제스처가 겹쳐서 혼란을 야기할 수 있다.

 

 

이런 부분에는 패딩이나 마진을 줘서 앱의 인터렉션 수행 영역에서 제외하면 좋을 것이다.

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())

    view.updatePadding(insets.left, insets.top, insets.right, insets.bottom)

    WindowInsetsCompat.CONSUMED
}

History

지금의 공식 문서에서 나온 내용만 보면 Edge to Edge 처리나 Window Inset 대응이 쉬워보이지만, 예전에는 그렇지 않았다. 신규 안드로이드 버전이 출시될 때마다 시스템 UI 가 그려지는 방식이나 배치, Window Inset 처리 방법 등이 계속해서 변경되어 대응을 해줘야 하거나 버전 분기가 들어가야 했다.

 

enableEdgeToEdge 확장 함수 내부를 보면 그러한 버전별 처리를 개발자 대신 해주고 있다는 것을 확인할 수 있다.

 

 

29 버전에서 처리

 

21 버전에서 처리

 

만약 프로젝트에서 Window Inset 처리를 하나로 통합하고 싶다면 각 버전별 처리와 유사한 코드가 직접 쓰여져 있는지 찾고 리펙토링 해주면 될것 같다.

 

이러한 기구한(?) 역사를 처음부터 추적해보고 싶다면 아래 영상 참고
https://www.youtube.com/watch?v=_mGDMVRO3iE

 

 

한국어로 된 자료는 아래 영상 참고
https://youtu.be/q6ZC4E4lAM8?feature=shared

 

728x90