데스크톱 창 모드를 사용하면 사용자가 크기 조절이 가능한 앱 창에서 여러 앱을 동시에 실행하여 다재다능한 데스크톱과 유사한 환경을 이용할 수 있습니다.
그림 1에서는 데스크톱 창 기능이 사용 설정된 화면의 구성을 확인할 수 있습니다. 다음 사항을 참고하세요.
- 사용자는 여러 앱을 동시에 나란히 실행할 수 있습니다.
- 태스크 바는 실행 중인 앱을 표시하는 디스플레이 하단에 고정되어 있습니다. 사용자는 앱을 고정하여 빠르게 액세스할 수 있습니다.
- 새로운 맞춤설정 가능한 헤더 표시줄은 최소화 및 최대화와 같은 컨트롤로 각 창의 상단을 장식합니다.
기본적으로 앱은 Android 태블릿에서 전체 화면으로 열립니다. 데스크톱 창에서 앱을 실행하려면 화면 상단의 창 핸들을 길게 누르고 UI 내에서 핸들을 드래그합니다(그림 2 참고).
앱이 데스크톱 윈도우로 열리면 다른 앱도 데스크톱 창으로 열립니다.
사용자는 핸들을 탭하거나 클릭하거나 키보드 단축키 Meta 키 (Windows, Command 또는 검색) + Ctrl + 아래를 사용할 때 창 핸들 아래에 표시되는 메뉴에서 데스크톱 창을 호출할 수도 있습니다.
사용자는 활성 창을 모두 닫거나 데스크톱 창 상단의 창 핸들을 잡고 앱을 화면 상단으로 드래그하여 데스크톱 창을 종료합니다. Meta + H 키보드 단축키를 사용하면 데스크톱 창이 종료되고 앱이 전체 화면으로 다시 실행됩니다.
데스크톱 창으로 돌아가려면 최근 항목 화면에서 데스크톱 스페이스 타일을 탭하거나 클릭합니다.
크기 조절 가능 여부 및 호환성 모드
데스크톱 윈도잉에서 방향이 잠긴 앱은 자유롭게 크기를 조절할 수 있습니다. 즉, 활동이 세로 모드로 잠겨 있어도 사용자는 앱을 가로 모드 창으로 크기를 조절할 수 있습니다.
크기 조절이 불가능하다고 선언된 앱 (즉, resizeableActivity = false)은 동일한 가로세로 비율을 유지하면서 UI가 조정됩니다.
방향을 잠그거나 크기 조절이 불가능하다고 선언된 카메라 앱은 카메라 뷰파인더에 특별한 처리가 적용됩니다. 창의 크기는 완전히 조절할 수 있지만 뷰파인더는 동일한 가로세로 비율을 유지합니다. 앱이 항상 세로 또는 가로로 실행된다고 가정하면 앱이 미리보기 또는 캡처된 이미지 방향이나 가로세로 비율을 잘못 계산하게 되는 가정을 하드코딩하거나 달리 가정하여 이미지가 늘어나거나 옆으로 기울어지거나 거꾸로 됩니다.
앱이 완전히 반응형 카메라 뷰파인더를 구현할 준비가 될 때까지 특수 처리는 잘못된 가정으로 인해 발생할 수 있는 영향을 완화하는 더 기본적인 사용자 환경을 제공합니다.
카메라 앱의 호환성 모드에 관해 자세히 알아보려면 기기 호환성 모드를 참고하세요.
맞춤설정 가능한 헤더 인셋
데스크톱 윈도잉에서 실행되는 모든 앱에는 몰입형 모드에서도 헤더 표시줄이 있습니다.
앱의 콘텐츠가 헤더 표시줄에 가려지지 않는지 확인합니다.
헤더 표시줄은 자막 표시줄 인셋 유형인 WindowInsets.Companion.captionBar()입니다. 뷰에서는 WindowInsets.Type.captionBar()이며 이는 시스템 표시줄의 일부입니다.
인셋 처리에 관한 자세한 내용은 앱에서 더 넓은 화면에 콘텐츠 표시 및 Compose에서 창 인셋 처리를 참고하세요.
헤더 표시줄도 맞춤설정할 수 있습니다. Android 15에서는 앱이 헤더 표시줄 내부에 맞춤 콘텐츠를 그릴 수 있도록 헤더 표시줄을 투명하게 만드는 모양 유형 APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND이 도입되었습니다.
그러면 앱은 시스템 자막 요소(닫기 및 최대화 버튼)를 제외하고 자막 표시줄처럼 보이도록 콘텐츠의 상단 부분을 스타일링해야 합니다. 시스템 자막 요소는 앱 상단의 투명한 자막 표시줄에 의해 그려집니다.
앱은 상태 표시줄과 탐색 메뉴가 전환되는 방식과 유사하게 APPEARANCE_LIGHT_CAPTION_BARS를 사용하여 밝은 테마와 어두운 테마의 자막 내에서 시스템 요소의 모양을 전환할 수 있습니다.
Android 15에서는 앱이 자막 표시줄 인셋을 더 자세히 인트로스펙션할 수 있는 WindowInsets#getBoundingRects() 메서드도 도입했습니다. 앱은 시스템이 시스템 요소를 그리는 영역과 시스템 요소와 겹치지 않고 앱이 맞춤 콘텐츠를 배치할 수 있는 미사용 영역을 구분할 수 있습니다.
API에서 반환된 Rect 객체 목록은 피해야 하는 시스템 영역을 나타냅니다. 남은 공간 (캡션 표시줄 인셋에서 사각형을 빼서 계산)은 앱이 시스템 요소와 겹치지 않고 입력을 받을 수 있는 공간입니다.
맞춤 헤더의 시스템 동작 제외 rect를 설정하려면 뷰나 컴포저블에서 다음을 구현하세요.
// In a custom View's onLayout or a similar lifecycle method
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (changed) {
// Calculate the height of your custom header
val customHeaderHeight = 100 // Replace with your actual header height in pixels
// Create a Rect covering your custom header area
val exclusionRect = Rect(0, 0, width, customHeaderHeight)
// Set the exclusion rects for the system
systemGestureExclusionRects = listOf(exclusionRect)
}
}
멀티태스킹 및 멀티 인스턴스 지원
멀티태스킹은 데스크톱 윈도우의 핵심이며 앱의 여러 인스턴스를 허용하면 사용자 생산성을 크게 높일 수 있습니다.
Android 15에서는 앱이 여러 인스턴스로 실행될 수 있도록 시스템 UI를 표시해야 한다고 지정하기 위해 앱이 설정할 수 있는 PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI이 도입되었습니다.
<activity> 태그 내 앱의 AndroidManifest.xml에서 PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI를 선언할 수 있습니다.
<activity
android:name=".MyActivity"
android:exported="true"
android:resizeableActivity="true">
<meta-data
android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
android:value="true" />
</activity>
드래그 동작으로 앱 인스턴스 관리
멀티 윈도우 모드에서 사용자는 앱 창에서 뷰 요소를 드래그하여 새 앱 인스턴스를 시작할 수 있습니다. 사용자는 동일한 앱의 인스턴스 간에 요소를 이동할 수도 있습니다.
Android 15에서는 드래그 동작을 맞춤설정하는 두 가지 플래그를 도입했습니다.
DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG: 표시되는 창에서 드롭을 처리하지 않는 경우 처리되지 않은 드래그가 시작되도록 시스템에 위임해야 함을 나타냅니다. 이 플래그를 사용하는 경우 호출자는 실행할 활동에 불변IntentSender이 포함된ClipData.Item를 사용하여ClipData를 제공해야 합니다(ClipData.Item.Builder#setIntentSender()참고). 시스템은 현재 화면 크기나 창 모드와 같은 요소를 기반으로 인텐트를 실행할 수도 있고 실행하지 않을 수도 있습니다. 시스템에서 인텐트를 실행하지 않으면 일반 드래그 흐름을 통해 인텐트가 취소됩니다.DRAG_FLAG_GLOBAL_SAME_APPLICATION: 드래그 작업이 창 경계를 교차할 수 있음을 나타냅니다 (동일한 애플리케이션의 여러 인스턴스).이 플래그가 설정된 상태에서 [
startDragAndDrop()][20]이 호출되면 동일한 애플리케이션에 속한 표시되는 창만 드래그 작업에 참여하고 드래그된 콘텐츠를 수신할 수 있습니다.
다음 예에서는 startDragAndDrop()와 함께 이러한 플래그를 사용하는 방법을 보여줍니다.
// Assuming 'view' is the View that initiates the drag
view.setOnLongClickListener {
// Create an IntentSender for the activity you want to launch
val launchIntent = Intent(view.context, NewInstanceActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
view.context,
0,
launchIntent,
PendingIntent.FLAG_IMMUTABLE // Ensure the PendingIntent is immutable
)
// Build the ClipData.Item with the IntentSender
val item = ClipData.Item.Builder()
.setIntentSender(pendingIntent.intentSender)
.build()
// Create ClipData with a simple description and the item
val dragData = ClipData(
ClipDescription("New Instance Drag", arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)),
item
)
// Combine the drag flags
val dragFlags = View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG or
View.DRAG_FLAG_GLOBAL_SAME_APPLICATION
// Start the drag operation
view.startDragAndDrop(
dragData, // The ClipData to drag
View.DragShadowBuilder(view), // A visual representation of the dragged item
null, // Local state object (not used here)
dragFlags // The drag flags
)
true // Indicate that the long click was consumed
}
추가 최적화
앱 실행을 맞춤설정하고 데스크톱 창에서 전체 화면으로 앱을 전환합니다.
기본 크기 및 위치 지정
크기 조정이 가능한 앱이라도 사용자 가치를 제공하는 데 큰 창이 필요하지 않을 수 있습니다.
ActivityOptions#setLaunchBounds() 메서드를 사용하여 활동이 시작될 때 기본 크기와 위치를 지정할 수 있습니다.
다음은 활동의 실행 경계를 설정하는 방법을 보여주는 예입니다.
val options = ActivityOptions.makeBasic()
// Define the desired launch bounds (left, top, right, bottom in pixels)
val launchBounds = Rect(100, 100, 700, 600) // Example: 600x500 window at (100,100)
// Apply the launch bounds to the ActivityOptions
options.setLaunchBounds(launchBounds)
// Start the activity with the specified options
val intent = Intent(this, MyActivity::class.java)
startActivity(intent, options.toBundle())
데스크톱 스페이스에서 전체 화면으로 전환
앱은 Activity#requestFullScreenMode()을 호출하여 전체 화면으로 전환할 수 있습니다.
이 메서드는 데스크톱 창에서 직접 앱을 전체 화면으로 표시합니다.
활동에서 전체 화면 모드를 요청하려면 다음 코드를 사용하세요.
// In an Activity
fun enterFullScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 15 (U)
requestFullScreenMode()
}
}