폴더블 디바이스용 Jetpack Window Manager

Jetpack Window Manager는 모든 폴더블 디바이스를 사용하기 위한 표준 API를 제공합니다. 여기에는 두 가지 중요한 클래스가 포함되어 있습니다.

  • DisplayFeature - 힌지 또는 접힌 부분과 같은 연속 평면 화면 표면의 중단을 식별합니다. Windows Manager는 레이아웃 변경 콜백에서 디스플레이 기능 컬렉션을 반환합니다.
  • FoldingFeature - 디바이스의 특정 기능에 대한 정보를 제공합니다. Surface Duo에는 폴딩 기능이 하나만 있지만 다른 디바이스에는 더 많은 기능이 있을 수 있습니다.

비슷한 가이드가 Android 폴더블 Codelab에 있습니다. Android 문서에서 폴더블 개발에 대해 자세히 알아보세요. Android 팀의 예도 GitHub에서 확인할 수 있습니다. Jetpack 릴리스 정보는 업데이트되는 Window Manager의 변경 사항을 기록합니다.

Surface Duo 이중 화면 라이브러리의 컨트롤 및 도우미 클래스는 Window Manager에서 작동합니다. 지침에 따라 앱 프로젝트에 올바른 패키지를 추가하세요.

코드에서 직접 Window Manager를 사용하려면 다음 지침을 따르세요.

종속성 추가

  1. 최상위 수준 build.gradle 파일에 mavenCentral() 리포지토리가 있는지 확인합니다.

    allprojects {
        repositories {
            google()
            mavenCentral()
         }
    }
    
  2. 모듈 수준 build.gradle 파일에서 compileSdkVersiontargetSdkVersion이 API 31 이상으로 설정되어 있는지 확인합니다.

    android { 
        compileSdkVersion 31
    
        defaultConfig { 
            targetSdkVersion 31
        } 
        ... 
    }
    
    
  3. 모듈 수준 build.gradle 파일에 다음 종속성을 추가합니다.

    dependencies {
        implementation "androidx.window:window:1.0.0"
        implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
    }
    

Kotlin 코드에서 Window Manager 사용

Kotlin 프로젝트에서 Window Manager 속성에 액세스할 때는 올바른 정보 흐름을 설정하는 것이 중요합니다. 그렇지 않으면 이벤트 업데이트가 너무 적거나 너무 많을 수 있으며 앱 성능이 영향을 받을 수 있습니다.

WindowInfoTracker 개체를 초기화하고 사용하려면 다음 단계를 수행합니다.

  1. MainActivity 클래스에서 WindowInfoTracker에 대한 변수를 만듭니다. 파일의 맨 위에 import androidx.window.layout.WindowInfoTracker가 추가되었는지 확인합니다.

    class MainActivity : AppCompatActivity() {
        private lateinit var windowInfoTracker: WindowInfoTracker
    
  2. 활동의 onCreate 메서드에서 WindowInfoTracker를 초기화하고 windowLayoutInfo 속성에서 정보를 수집하도록 흐름을 설정합니다.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        // Initialize the window manager
        windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity)
    
        // Set up a flow
        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                windowInfoTracker.windowLayoutInfo(this@MainActivity)
                    .collect { 
                        // Check FoldingFeature properties here
                    }
            }
        }
    }
    

    이러한 가져오기가 파일 맨 위에도 추가되었는지 확인합니다.

    import androidx.lifecycle.Lifecycle
    import androidx.lifecycle.lifecycleScope
    import androidx.lifecycle.repeatOnLifecycle
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.flow.collect
    import kotlinx.coroutines.launch
    
  3. 접기 기능 속성에 대한 WindowLayoutInfo 흐름을 확인하는 코드를 추가합니다. 이 코드가 실행되면 현재 디바이스 상태와 표시 기능으로 활동이 업데이트됩니다(접힌 부분 또는 힌지에 걸쳐 있는 경우).

    아래 코드 조각에서 작업은 FoldingFeature의 속성에 따라 다른 텍스트를 표시합니다.

    이 예제에는 검색된 모든 접기 기능에 대한 폐색 유형 및 isSeparating 값을 보여 주는 layout_change_text라는 TextView가 있습니다.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        windowInfoTracker = WindowInfoTracker.getOrCreate(this@MainActivity)
    
        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                windowInfoTracker.windowLayoutInfo(this@MainActivity)
                    .collect { newLayoutInfo ->
                        layout_change_text.text = "No display features detected"
                        for (displayFeature : DisplayFeature in newLayoutInfo.displayFeatures) {
                            if (displayFeature is FoldingFeature && displayFeature.occlusionType == FoldingFeature.OcclusionType.NONE) {
                                layout_change_text.text = "App is spanned across a fold, " +
                                    "isSeparating = ${displayFeature.isSeparating}"
                            }
                            if (displayFeature is FoldingFeature && displayFeature.occlusionType == FoldingFeature.OcclusionType.FULL) {
                                layout_change_text.text = "App is spanned across a hinge, " +
                                    "isSeparating = ${displayFeature.isSeparating}"
                            }
                        }
                    }
            }
        }
    }
    

접기 기능 속성

WindowLayoutInfo 클래스에는 DisplayFeature 항목 컬렉션이 있으며, 그중 하나 이상은 FoldingFeature class의 인스턴스일 수 있습니다.

접기 기능에는 다음과 같은 속성이 있습니다.

  • bounds - 접는 기능의 경계 사각형 좌표
  • occlusionType - 접기 기능이 콘텐츠를 숨기는 경우(FULL 또는 NONE)
  • orientation - 접기 기능의 방향(HORIZONTAL 또는 VERTICAL)
  • state - 접기 기능의 각도(HALF_OPENED 또는 FLAT)
  • isSeparating - 접기 기능이 디스플레이 영역을 두 개의 개별 섹션으로 구분하는 경우

이러한 속성을 쿼리하여 구성 변경 후 레이아웃을 조정하는 방법을 결정할 수 있습니다.

isSeparating

컨트롤을 배치할 위치 또는 표시할 콘텐츠 창 수를 결정할 때 isSeparating 속성을 사용합니다. 이 필드는 앱이 모든 폴더블 디바이스에서 최상의 사용자 환경을 제공하는지 확인합니다.

  • 이중 화면 디바이스의 경우 앱이 힌지에 걸쳐 있을 때 항상 적용됩니다.
  • 다른 폴더블 디바이스의 경우 디바이스가 탁상 상태에 있는 경우와 같이 상태가 HALF_OPENED인 경우에만 적용됩니다.

isSeparating 속성을 사용하여 앱의 UI 레이아웃을 폴더블 디바이스에 맞게 조정할지 또는 분리가 없는 경우 기본 UI를 사용할지 결정합니다.

private fun updateCurrentLayout(newLayoutInfo: WindowLayoutInfo) {
   for (displayFeature in newLayoutInfo.displayFeatures) {
       val foldFeature = displayFeature as? FoldingFeature
       foldFeature?.let {
           if (it.isSeparating) {
               // The content is separated by the FoldingFeature.
               // Here is where you should adapt your UI.
           } else {
               // The content is not separated.
               // Users can see and interact with your UI properly.
           }
       }
   }
}

이 필드를 사용하는 방법에 대한 보다 자세한 예제를 보려면 이 isSeparating 샘플을 확인하세요.

또한 Google은 이 속성과 관련된 설명서샘플을 대형 화면 및 폴더블 지침의 일부로 제공합니다.

샘플

surface-duo-jetpack-window-manager-samples GitHub 리포지토리에는 Jetpack Window Manager와 기존 보기 시스템을 사용하여 구축된 다양한 이중 화면 사용자 환경 패턴을 보여주는 여러 Kotlin 샘플이 포함되어 있습니다.

surface-duo-compose-samples GitHub 리포지토리에는 Jetpack Window Manager를 사용하는 이중 화면 Kotlin 샘플도 있지만 이러한 샘플에서는 UI가 Jetpack Compose로 빌드됩니다.

Java API

WindowInfoTrackerCallbackAdapter를 통해 WindowInfoTracker 클래스에 액세스하는 방법은 Jetpack Window Manager with Java 블로그 게시물 및 이 Java 샘플을 참조하세요.

리소스