รองรับการจัดหน้าต่างเดสก์ท็อป

หน้าต่างเดสก์ท็อปช่วยให้ผู้ใช้เรียกใช้หลายแอปพร้อมกันในหน้าต่างแอปที่ปรับขนาดได้ เพื่อประสบการณ์การใช้งานที่หลากหลายและเหมือนกับบนเดสก์ท็อป

ในรูปที่ 1 คุณจะเห็นการจัดระเบียบหน้าจอเมื่อเปิดใช้การแสดงหน้าต่างบนเดสก์ท็อป สิ่งที่ควรทราบ

  • ผู้ใช้สามารถเรียกใช้แอปหลายแอปแบบคู่ขนานพร้อมกันได้
  • แถบงานจะอยู่ในตำแหน่งคงที่ที่ด้านล่างของจอแสดงผลซึ่งแสดง แอปที่กำลังทำงาน ผู้ใช้สามารถปักหมุดแอปเพื่อการเข้าถึงที่รวดเร็ว
  • แถบส่วนหัวแบบใหม่ที่ปรับแต่งได้จะตกแต่งด้านบนของแต่ละหน้าต่างด้วยตัวควบคุม เช่น ย่อและขยาย
จอแสดงผลของแท็บเล็ตแสดงแอปหลายแอปที่ทำงานในหน้าต่างที่ปรับขนาดได้โดยมีแถบงานที่ด้านล่าง
รูปที่ 1 หน้าต่างเดสก์ท็อปบนแท็บเล็ต

โดยค่าเริ่มต้นแล้ว แอปจะเปิดแบบเต็มหน้าจอในแท็บเล็ต Android หากต้องการเปิดแอปในโหมดการแสดงหน้าต่างบนเดสก์ท็อป ให้กดที่แฮนเดิลหน้าต่างค้างไว้ที่ด้านบนของหน้าจอ แล้วลากแฮนเดิลภายใน UI ดังที่แสดงในรูปที่ 2

เมื่อแอปเปิดในหน้าต่างเดสก์ท็อป แอปอื่นๆ จะเปิดในหน้าต่างเดสก์ท็อปด้วย

รูปที่ 2 กดค้างไว้ แล้วลากแฮนเดิลหน้าต่างแอปเพื่อเข้าสู่ การแสดงหน้าต่างบนเดสก์ท็อป

นอกจากนี้ ผู้ใช้ยังเรียกใช้การจัดหน้าต่างบนเดสก์ท็อปจากเมนูที่ปรากฏใต้แฮนเดิลของหน้าต่างได้เมื่อแตะหรือคลิกแฮนเดิล หรือใช้แป้นพิมพ์ลัด Meta (Windows, Command หรือ Search) + Ctrl + ลูกศรลง

ผู้ใช้ออกจากโหมดหลายหน้าต่างบนเดสก์ท็อปได้โดยการปิดหน้าต่างที่ใช้งานอยู่ทั้งหมด หรือโดยการจับ แฮนเดิลหน้าต่างที่ด้านบนของหน้าต่างเดสก์ท็อป แล้วลากแอปไปที่ด้านบน ของหน้าจอ แป้นพิมพ์ลัด Meta + H จะออกจาก การแสดงหน้าต่างบนเดสก์ท็อปและเรียกใช้แอปแบบเต็มหน้าจออีกครั้งด้วย

หากต้องการกลับไปใช้การจัดหน้าต่างบนเดสก์ท็อป ให้แตะหรือคลิกการ์ดพื้นที่เดสก์ท็อปใน หน้าจอ "ล่าสุด"

โหมดปรับขนาดและความเข้ากันได้

ในระบบหน้าต่างบนเดสก์ท็อป แอปที่มีการวางแนวที่ล็อกจะปรับขนาดได้อย่างอิสระ ซึ่งหมายความว่าแม้ว่ากิจกรรมจะล็อกเป็นแนวตั้ง ผู้ใช้ก็ยังปรับขนาดแอปให้เป็นหน้าต่างแนวนอนได้

รูปที่ 3 การปรับขนาดหน้าต่างของแอปที่จำกัดให้แสดงในแนวตั้งเป็นแนวนอน

แอปที่ประกาศว่าปรับขนาดไม่ได้ (เช่น resizeableActivity = false) จะมี UI ที่ปรับขนาดในขณะที่ยังคงสัดส่วนภาพเดิมไว้

รูปที่ 4 UI ของแอปที่ปรับขนาดไม่ได้จะปรับขนาดตามการปรับขนาดหน้าต่าง

แอปกล้องที่ล็อกการวางแนวหรือประกาศว่าปรับขนาดไม่ได้จะมี การจัดการพิเศษสำหรับช่องมองภาพของกล้อง โดยหน้าต่างจะปรับขนาดได้อย่างเต็มที่ แต่ช่องมองภาพจะยังคงอัตราส่วนภาพเดิม การถือว่าแอปทำงานในโหมดแนวตั้งหรือแนวนอนเสมอทำให้แอปฮาร์ดโค้ดหรือทำการสมมติฐานอื่นๆ ที่ทำให้เกิดการคำนวณการวางแนวหรือสัดส่วนภาพของตัวอย่างหรือรูปภาพที่บันทึกไว้ผิดพลาด ส่งผลให้รูปภาพยืดออก ตะแคง หรือกลับหัว

จนกว่าแอปจะพร้อมใช้งานช่องมองภาพของกล้องที่ตอบสนองอย่างเต็มรูปแบบ การจัดการพิเศษนี้จะมอบประสบการณ์ของผู้ใช้ขั้นพื้นฐานมากขึ้น ซึ่งจะช่วยลด ผลกระทบที่อาจเกิดจากสมมติฐานที่ไม่ถูกต้อง

ดูข้อมูลเพิ่มเติมเกี่ยวกับโหมดความเข้ากันได้สำหรับแอปกล้องได้ที่โหมดความเข้ากันได้ของอุปกรณ์

รูปที่ 5 ช่องมองภาพของกล้องจะยังคงอัตราส่วนเดิมไว้เมื่อ หน้าต่างปรับขนาด

ระยะขอบส่วนหัวที่ปรับแต่งได้

แอปทั้งหมดที่ทำงานในระบบหน้าต่างบนเดสก์ท็อปจะมีแถบส่วนหัว แม้ในโหมดสมจริง ตรวจสอบว่าแถบส่วนหัวไม่ได้บดบังเนื้อหาของแอป แถบส่วนหัวคือประเภทแท็บคำบรรยายแทนเสียงที่แทรก: WindowInsets.Companion.captionBar(); ในมุมมอง WindowInsets.Type.captionBar() ซึ่งเป็นส่วนหนึ่งของแถบระบบ

ดูข้อมูลเพิ่มเติมเกี่ยวกับการจัดการ Inset ได้ที่ แสดงเนื้อหาแบบขอบจรดขอบในแอปและจัดการ Window Inset ใน Compose

แถบส่วนหัวยังปรับแต่งได้ด้วย Android 15 ได้เปิดตัวประเภทลักษณะที่ปรากฏ APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND เพื่อทำให้ แถบส่วนหัวโปร่งใสเพื่อให้แอปวาดเนื้อหาที่กำหนดเองภายใน แถบส่วนหัวได้

จากนั้นแอปจะมีหน้าที่รับผิดชอบในการจัดรูปแบบส่วนบนของเนื้อหาให้ ดูเหมือนแถบคำบรรยายแทนเสียง (พื้นหลัง เนื้อหาที่กำหนดเอง และอื่นๆ) โดย ยกเว้นองค์ประกอบคำบรรยายแทนเสียงของระบบ (ปุ่มปิดและปุ่มขยาย) ซึ่งระบบจะวาดบนแถบคำบรรยายแทนเสียงแบบโปร่งใสที่ด้านบนของแอป

แอปสามารถสลับลักษณะขององค์ประกอบของระบบภายในคำบรรยายแทนเสียงสำหรับ ธีมสว่างและธีมมืดได้โดยใช้ APPEARANCE_LIGHT_CAPTION_BARS ในลักษณะเดียวกับการสลับแถบสถานะและแถบนำทาง

Android 15 ยังได้เปิดตัวเมธอด WindowInsets#getBoundingRects() ซึ่งช่วยให้แอปสามารถตรวจสอบระยะขอบของแถบคำบรรยายแทนเสียงได้อย่างละเอียดมากขึ้น แอปสามารถ แยกแยะระหว่างพื้นที่ที่ระบบวาดองค์ประกอบของระบบกับ พื้นที่ที่ไม่ได้ใช้ซึ่งแอปสามารถวางเนื้อหาที่กำหนดเองได้โดยไม่ทับซ้อนกับองค์ประกอบของระบบ

รายการออบเจ็กต์ Rect ที่ API แสดงผลจะระบุภูมิภาคของระบบที่ ควรหลีกเลี่ยง พื้นที่ที่เหลือ (คำนวณโดยการลบสี่เหลี่ยมผืนผ้า ออกจาก Insets ของแถบคำบรรยายแทนเสียง) คือตำแหน่งที่แอปวาดได้โดยไม่ ทับซ้อนกับองค์ประกอบของระบบและรับอินพุตได้

Chrome ก่อนและหลังการติดตั้งใช้งานส่วนหัวที่กำหนดเอง
รูปที่ 6 Chrome ก่อนและหลังการติดตั้งใช้งานส่วนหัวที่กำหนดเอง

หากต้องการตั้งค่าสี่เหลี่ยมผืนผ้าการยกเว้นท่าทางสัมผัสของระบบสำหรับส่วนหัวที่กำหนดเอง ให้ใช้ รายการต่อไปนี้ใน View หรือ Composable

// 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 ขอแนะนำ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI ซึ่งแอปสามารถตั้งค่าเพื่อระบุว่าควรแสดง UI ของระบบสำหรับแอปเพื่อ อนุญาตให้เปิดเป็นหลายอินสแตนซ์ได้

คุณประกาศ PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI ใน AndroidManifest.xml ของแอปได้ ภายในแท็ก <activity> ดังนี้

<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>

จัดการอินสแตนซ์ของแอปด้วยท่าทางสัมผัสการลาก

ในโหมดหลายหน้าต่าง ผู้ใช้สามารถเริ่มอินสแตนซ์แอปใหม่ได้โดยการลากองค์ประกอบมุมมอง ออกจากหน้าต่างของแอป นอกจากนี้ ผู้ใช้ยังย้ายองค์ประกอบระหว่างอินสแตนซ์ของแอปเดียวกันได้ด้วย

รูปที่ 7 เริ่มอินสแตนซ์ใหม่ของ Chrome โดยการลากแท็บออกจาก หน้าต่างเดสก์ท็อป

Android 15 มีฟีเจอร์ 2 อย่างที่ช่วยปรับแต่งลักษณะการทำงานของการลาก ได้แก่

  • DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG: ระบุว่าควรส่งต่อการลากที่ไม่ได้จัดการไปยังระบบเพื่อเริ่ม หากไม่มีหน้าต่างที่มองเห็นได้จัดการการวาง เมื่อใช้ Flag นี้ ผู้เรียกใช้ต้องระบุ ClipData ที่มี ClipData.Item ซึ่งมี IntentSender ที่เปลี่ยนแปลงไม่ได้ไปยังกิจกรรมที่จะเปิดใช้ (ดู ClipData.Item.Builder#setIntentSender()) ระบบจะเปิดใช้ Intent หรือไม่ก็ได้โดยอิงตามปัจจัยต่างๆ เช่น ขนาดหน้าจอปัจจุบันหรือโหมดการแสดงหน้าต่าง หากระบบไม่เปิดใช้ Intent ระบบจะยกเลิก Intent โดยใช้โฟลว์การลากปกติ

  • 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
}
รูปที่ 8 ย้ายแท็บระหว่างแอป Chrome 2 อินสแตนซ์

การเพิ่มประสิทธิภาพเพิ่มเติม

ปรับแต่งการเปิดแอปและเปลี่ยนแอปจากการแสดงหน้าต่างบนเดสก์ท็อปเป็นเต็มหน้าจอ

ระบุขนาดและตำแหน่งเริ่มต้น

แอปบางแอปไม่จำเป็นต้องมีหน้าต่างขนาดใหญ่เพื่อมอบมูลค่าแก่ผู้ใช้ แม้ว่าจะปรับขนาดได้ก็ตาม คุณสามารถใช้เมธอด 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()
    }
}