หน้าต่างเดสก์ท็อปช่วยให้ผู้ใช้เรียกใช้หลายแอปพร้อมกันในหน้าต่างแอปที่ปรับขนาดได้เพื่อประสบการณ์การใช้งานที่หลากหลายเหมือนเดสก์ท็อป
ในรูปที่ 1 คุณจะเห็นการจัดระเบียบหน้าจอที่เปิดใช้หน้าต่างเดสก์ท็อป สิ่งที่ควรทราบ
- ผู้ใช้สามารถเรียกใช้แอปหลายแอปพร้อมกันได้
- แถบงานจะอยู่ในตำแหน่งคงที่ที่ด้านล่างของจอแสดงผลซึ่งแสดงแอปที่ทำงานอยู่ ผู้ใช้สามารถปักหมุดแอปเพื่อให้เข้าถึงได้อย่างรวดเร็ว
- แถบส่วนหัวใหม่ที่ปรับแต่งได้จะตกแต่งด้านบนของแต่ละหน้าต่างด้วยตัวควบคุมต่างๆ เช่น ย่อและขยายใหญ่สุด
โดยค่าเริ่มต้น แอปจะเปิดแบบเต็มหน้าจอในแท็บเล็ต Android หากต้องการเปิดแอปในหน้าต่างเดสก์ท็อป ให้กดแฮนเดิลหน้าต่างที่ด้านบนของหน้าจอค้างไว้ แล้วลากแฮนเดิลภายใน UI ดังที่เห็นในรูปที่ 2
เมื่อแอปเปิดอยู่ในหน้าต่างเดสก์ท็อป แอปอื่นๆ ก็จะเปิดในหน้าต่างเดสก์ท็อปด้วยเช่นกัน
ผู้ใช้ยังเรียกใช้หน้าต่างเดสก์ท็อปได้จากเมนูที่ปรากฏใต้แฮนเดิลของหน้าต่างเมื่อแตะหรือคลิกแฮนเดิล หรือใช้แป้นพิมพ์ลัดแป้น Meta (Windows, Command หรือ Search) + Ctrl + Down
ผู้ใช้จะออกจากโหมดหน้าต่างเดสก์ท็อปโดยปิดหน้าต่างที่ใช้งานอยู่ทั้งหมด หรือโดยการจับแฮนเดิลหน้าต่างที่ด้านบนของหน้าต่างเดสก์ท็อปแล้วลากแอปไปที่ด้านบนของหน้าจอ แป้นพิมพ์ลัด Meta + H ยังใช้เพื่อออกจากโหมดหน้าต่างเดสก์ท็อปและเรียกใช้แอปแบบเต็มหน้าจออีกครั้งได้ด้วย
หากต้องการกลับไปที่หน้าต่างเดสก์ท็อป ให้แตะหรือคลิกการ์ดพื้นที่เดสก์ท็อปในหน้าจอ "ล่าสุด"
เพิ่มประสิทธิภาพเลย์เอาต์แอปสำหรับสภาพแวดล้อมที่คล้ายกับเดสก์ท็อป
การออกแบบสำหรับประสบการณ์การใช้งานบนเดสก์ท็อปอาจแตกต่างจากการออกแบบสำหรับอุปกรณ์เคลื่อนที่อย่างมาก เนื่องจากพื้นที่หน้าจอที่เพิ่มขึ้น ความแม่นยำของการป้อนข้อมูลด้วยเมาส์และแป้นพิมพ์ และความคาดหวังในประสิทธิภาพการทำงานที่สูง
Jetpack WindowManager มี API ที่แสดงความคิดเห็นเพื่อช่วยนักพัฒนาแอปตัดสินใจว่าจะแสดง UI บนเดสก์ท็อปเมื่อใด ซึ่งโดยปกติแล้วจะมีความหนาแน่นของข้อมูลสูงกว่า รูปแบบการนำทางที่แตกต่างกัน และการโต้ตอบด้วยเมาส์ที่เพิ่มประสิทธิภาพ
lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { windowInfoTracker.windowEngagementInfo(this@DesktopWindowingActivity) .collect { windowEngagementInfo -> if(windowEngagementInfo.hasEngagementMode(WindowEngagementInfo.EngagementMode.PRECISE_POINTER)){ showDesktopOptimizedUI() }else { showTouchOptimizedUI() } } } }
ดูข้อมูลเพิ่มเติมได้ที่ออกแบบสำหรับเดสก์ท็อป
ความสามารถในการปรับขนาดและโหมดความเข้ากันได้
ในหน้าต่างเดสก์ท็อป คุณสามารถปรับขนาดแอปที่มีการล็อกการวางแนวได้อย่างอิสระ ซึ่งหมายความว่าแม้ว่ากิจกรรมจะล็อกไว้ที่การวางแนวตั้ง ผู้ใช้ก็ยังคงปรับขนาดแอปเป็นหน้าต่างการวางแนวนอนได้
แอปที่ประกาศว่าปรับขนาดไม่ได้ (นั่นคือ resizeableActivity = false) จะมีการปรับขนาด UI โดยยังคงสัดส่วนภาพเดิมไว้
แอปกล้องที่ล็อกการวางแนวหรือประกาศว่าปรับขนาดไม่ได้จะมีการจัดการพิเศษสำหรับช่องมองภาพของกล้อง กล่าวคือ หน้าต่างจะปรับขนาดได้อย่างเต็มที่ แต่ช่องมองภาพจะยังคงมีสัดส่วนภาพเท่าเดิม การสมมติว่าแอปทำงานในแนวตั้งหรือแนวนอนเสมอจะทำให้แอปฮาร์ดโค้ดหรือตั้งสมมติฐานที่นำไปสู่การคำนวณการวางแนวหรือสัดส่วนภาพของตัวอย่างหรือรูปภาพที่ถ่ายผิดพลาด ซึ่งส่งผลให้รูปภาพถูกยืดออก ตะแคงข้าง หรือกลับหัว
จนกว่าแอปจะพร้อมใช้ช่องมองภาพของกล้องที่ปรับเปลี่ยนตามอุปกรณ์ได้อย่างเต็มรูปแบบ การจัดการพิเศษจะมอบประสบการณ์ของผู้ใช้ขั้นพื้นฐานมากขึ้น ซึ่งจะช่วยลดผลกระทบที่อาจเกิดจากสมมติฐานที่ไม่ถูกต้อง
ดูข้อมูลเพิ่มเติมเกี่ยวกับโหมดความเข้ากันได้สำหรับแอปกล้องได้ที่โหมดความเข้ากันได้ของอุปกรณ์
ส่วนแทรกส่วนหัวที่ปรับแต่งได้
แอปทั้งหมดที่ทำงานในหน้าต่างเดสก์ท็อปจะมีแถบส่วนหัว แม้จะอยู่ในโหมดใหญ่พิเศษก็ตาม คุณปรับแต่งแถบนี้เพื่อป้องกันไม่ให้เนื้อหาของแอปถูกบดบังและวาดองค์ประกอบ UI ที่กำหนดเองลงในพื้นที่ส่วนหัวโดยตรงได้
การใช้งาน
หากต้องการวาดเนื้อหาที่กำหนดเองในแถบส่วนหัว ขั้นตอนแรกคือทำให้พื้นหลังของแถบส่วนหัวโปร่งใส คุณสามารถทำได้โดยใช้แฟล็ก APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND กับ WindowInsetsController
window.insetsController?.setSystemBarsAppearance( WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND )
เมื่อแถบส่วนหัวโปร่งใสแล้ว คุณสามารถจัดสไตล์พื้นที่ส่วนหัวให้เข้ากับการออกแบบของแอปได้ ใช้ WindowInsets.isCaptionBarVisible เพื่อตรวจหาว่ามีแถบอยู่หรือไม่ และใช้ความสูงหรือระยะห่างจากขอบที่เหมาะสมกับเลย์เอาต์
@OptIn(ExperimentalLayoutApi::class) @Composable fun CaptionBar() { if (WindowInsets.isCaptionBarVisible) { Row( modifier = Modifier .windowInsetsTopHeight(WindowInsets.captionBar) .fillMaxWidth() .background(if (isSystemInDarkTheme()) Color.White else Color.Black), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { Text( text = "Caption Bar Title", style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(4.dp) ) } } }
setSystemBarsAppearance(appearance,mask): กำหนดค่ารูปแบบภาพของแถบระบบ พารามิเตอร์แรกจะกำหนดแฟล็กรูปลักษณ์เป้าหมาย ส่วนพารามิเตอร์ที่ 2 จะทำหน้าที่เป็นมาสก์เพื่อควบคุมว่าจะแก้ไขแฟล็กใดโดยเฉพาะwindowInsetsTopHeight(): ตั้งค่าความสูงของ Composable โดยอัตโนมัติให้ตรงกับแถบส่วนหัวของระบบ ซึ่งจะช่วยให้พื้นหลังที่กำหนดเองเติมเต็มพื้นที่คำบรรยายแทนเสียงโดยไม่ต้องฮาร์ดโค้ดค่าพิกเซลWindowInsets.captionBar: ระบุขนาดสำหรับการควบคุมหน้าต่างเดสก์ท็อป (ปิด ขยายใหญ่สุด ฯลฯ) ซึ่งช่วยให้ UI ปรับขนาดหรือซ่อนโดยอัตโนมัติเมื่อเข้าสู่หรือออกจากหน้าต่างเดสก์ท็อป
ดูข้อมูลเพิ่มเติมได้ที่เกี่ยวกับส่วนแทรกของหน้าต่าง นอกจากชื่อแล้ว คุณยังแสดงองค์ประกอบ UI อื่นๆ ในแถบคำอธิบายภาพได้ด้วย เช่น แท็บ (เหมือนใน Google Chrome) แถบค้นหา หรือรูปโปรไฟล์
อินเทอร์เฟซผู้ใช้
เพื่อหลีกเลี่ยงไม่ให้ UI ซ้อนทับกับปุ่มระบบ Android 15 มีเมธอด WindowInsets#getBoundingRects() เมธอดจะแสดงรายการออบเจ็กต์ Rect ที่แสดงพื้นที่ซึ่งองค์ประกอบของระบบครอบครองอยู่ พื้นที่ที่เหลือในแถบคำบรรยายภาพคือโซนปลอดภัยที่คุณสามารถวางเนื้อหาที่กำหนดเองได้อย่างปลอดภัย
สลับลักษณะที่ปรากฏขององค์ประกอบคำบรรยายแทนเสียงของระบบสำหรับธีมสว่างและธีมมืดโดยใช้ APPEARANCE_LIGHT_CAPTION_BARS เข้าถึงส่วนแทรกโดยใช้ WindowInsets.Companion.captionBar() ใน Compose หรือ WindowInsets.Type.captionBar() ใน Views
ดูข้อมูลเพิ่มเติมได้ที่เกี่ยวกับส่วนแทรกของหน้าต่าง
การรองรับการทำงานหลายอย่างพร้อมกันและอินสแตนซ์หลายรายการ
การทำงานหลายอย่างพร้อมกันเป็นหัวใจสำคัญของการจัดหน้าต่างเดสก์ท็อป และการอนุญาตให้อินสแตนซ์หลายรายการของแอปทำงานพร้อมกันได้จะช่วยเพิ่มประสิทธิภาพการทำงานของผู้ใช้ได้อย่างมาก
ตั้งแต่ Android 15 เป็นต้นไป คุณสามารถใช้ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI ได้ การตั้งค่าพร็อพเพอร์ตี้นี้ใน AndroidManifest.xml จะเป็นการระบุว่า UI ของระบบควรมีตัวเลือก (เช่น ปุ่ม "หน้าต่างใหม่") เพื่อให้แอปเปิดได้หลายอินสแตนซ์
<application>
<property
android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
android:value="true" />
</application>
หมายเหตุ: ในหน้าต่างเดสก์ท็อปและสภาพแวดล้อมแบบหลายหน้าต่างอื่นๆ งานใหม่จะเปิดในหน้าต่างใหม่ ดังนั้นโปรดตรวจสอบเส้นทางของผู้ใช้อีกครั้งทุกครั้งที่แอปเริ่มงานหลายรายการ
จัดการอินสแตนซ์ของแอปด้วยท่าทางการลาก
ในโหมดหลายหน้าต่าง ผู้ใช้สามารถเริ่มอินสแตนซ์แอปใหม่ได้โดยการลากองค์ประกอบ UI (เช่น แท็บหรือเอกสาร) ออกจากหน้าต่างของแอป นอกจากนี้ ผู้ใช้ยังย้ายองค์ประกอบระหว่างอินสแตนซ์ต่างๆ ของแอปเดียวกันได้ด้วย
โอนข้อมูลด้วยการลากและวาง
หากต้องการกำหนดค่า Composable เป็นแหล่งที่มาของการลากสำหรับการลากและวางแบบอินสแตนซ์หลายรายการ ซึ่งช่วยให้ผู้ใช้ลากเนื้อหาไปยังอินสแตนซ์อื่นของแอป หรือสร้างอินสแตนซ์ใหม่โดยการวางเนื้อหาลงในพื้นที่ว่างของหน้าจอ ให้ใช้ตัวปรับแต่ง dragAndDropSource ใน Lambda ให้ส่งคืน DragAndDropTransferData โดยส่ง ClipData ที่มีข้อมูลที่จะโอน และแฟล็กเพื่อกำหนดค่าลักษณะการทำงานแบบอินสแตนซ์หลายรายการ
Android 15 มี Flag สำคัญ 2 รายการสำหรับการจัดการหน้าต่างแบบเดสก์ท็อปและการโต้ตอบแบบหลายอินสแตนซ์ ดังนี้
DRAG_FLAG_GLOBAL_SAME_APPLICATION: ระบุว่าการดำเนินการลากสามารถข้ามขอบเขตหน้าต่างได้ (สำหรับอินสแตนซ์หลายรายการของแอปพลิเคชันเดียวกัน) เมื่อเรียกใช้startDragAndDrop()โดยตั้งค่าแฟล็กนี้ไว้ เฉพาะหน้าต่างที่มองเห็นได้ซึ่งเป็นของแอปพลิเคชันเดียวกันเท่านั้นที่จะเข้าร่วมการดำเนินการลากและรับเนื้อหาที่ลากได้
Modifier.dragAndDropSource { _ -> DragAndDropTransferData( clipData = ClipData.newPlainText("label", "Your data"), flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION ) }
DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG: ช่วยให้ผู้ใช้เริ่มอินสแตนซ์ใหม่ของแอปได้โดยการวางเนื้อหาที่ลากลงในพื้นที่ว่างของหน้าจอ หากไม่มีหน้าต่างอื่นจัดการการวาง- เมื่อใช้แฟล็กนี้ คุณต้องระบุ
IntentSenderโดยใช้ClipData.Item.Builder#setIntentSender()ซึ่งระบบจะใช้เพื่อเปิดกิจกรรมใหม่หากเกิดการวางที่ไม่มีการจัดการ
- เมื่อใช้แฟล็กนี้ คุณต้องระบุ
Modifier.dragAndDropSource { _ -> val intent = Intent.makeMainActivity(activity.componentName).apply { putExtra("EXTRA_ITEM_ID", itemId) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK or Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT } val pendingIntent = PendingIntent.getActivity( activity, 0, intent, PendingIntent.FLAG_IMMUTABLE ) val data = ClipData( "Item $itemId", arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT), ClipData.Item.Builder().setIntentSender(pendingIntent.intentSender).build() ) DragAndDropTransferData( clipData = data, flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG, ) }
รับข้อมูลที่โอนมา
หากต้องการยอมรับข้อมูลจากอินสแตนซ์อื่น ให้ใช้ตัวแก้ไข dragAndDropTarget
คุณต้องขอสิทธิ์อย่างชัดแจ้งหากข้อมูลมาจากอินสแตนซ์หรือแอปอื่น
Modifier.dragAndDropTarget( shouldStartDragAndDrop = { event -> event.toAndroidDragEvent().clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) }, target = object : DragAndDropTarget { override fun onDrop(event: DragAndDropEvent): Boolean { requestDragAndDropPermissions(activity, event.toAndroidDragEvent()) val clipData = event.toAndroidDragEvent().clipData val item = clipData?.getItemAt(0)?.text if (item != null) { // Process the dropped text item here } return item != null } } )
ขั้นตอนสำคัญ
- ตัวกรอง: ใช้
shouldStartDragAndDropเพื่อตรวจสอบว่าระบบรองรับข้อมูลขาเข้า (ประเภท MIME) หรือไม่ - สิทธิ์: โทรหา
requestDragAndDropPermissions(event)เพื่อเข้าถึงข้อมูล - จัดการ: ดึงข้อมูลใน Callback
onDrop
การเพิ่มประสิทธิภาพเพิ่มเติม
ปรับแต่งการเปิดแอปและเปลี่ยนแอปจากโหมดหน้าต่างเดสก์ท็อปเป็นโหมดเต็มหน้าจอ
ระบุขนาดและตำแหน่งเริ่มต้น
แอปบางแอปไม่จำเป็นต้องมีหน้าต่างขนาดใหญ่เพื่อมอบคุณค่าให้แก่ผู้ใช้ แม้ว่าจะปรับขนาดได้ก็ตาม คุณสามารถใช้เมธอด ActivityOptions#setLaunchBounds() เพื่อระบุขนาดและตำแหน่งเริ่มต้นเมื่อเปิดใช้งานกิจกรรม
เข้าสู่โหมดเต็มหน้าจอจากพื้นที่เดสก์ท็อป
แอปสามารถแสดงแบบเต็มหน้าจอได้โดยเรียกใช้ Activity#requestFullScreenMode() เมธอดนี้จะแสดงแอปแบบเต็มหน้าจอโดยตรงจากหน้าต่างเดสก์ท็อป