แสดงเนื้อหาแบบไร้ขอบในแอปและจัดการส่วนหน้าต่างใน Compose

แพลตฟอร์ม Android รับผิดชอบการวาด UI ของระบบ เช่น แถบสถานะและแถบนำทาง UI ระบบนี้จะแสดงขึ้นไม่ว่าผู้ใช้จะใช้แอปใดก็ตาม

WindowInsets ให้ข้อมูลเกี่ยวกับ UI ของระบบเพื่อให้มั่นใจว่าแอปจะวาดในพื้นที่ที่ถูกต้องและ UI ของคุณจะไม่ถูกบดบังโดย UI ของระบบ

วาดจากขอบหนึ่งไปยังอีกขอบหนึ่งเพื่อวาดหลังแถบระบบ
ภาพที่ 1 วาดจากขอบถึงขอบเพื่อวาดหลังแถบระบบ

ใน Android 14 (API ระดับ 34) และต่ำกว่า UI ของแอปจะไม่วาดใต้แถบระบบและส่วนที่ถูกตัดออกของจอแสดงผลโดยค่าเริ่มต้น

ใน Android 15 (API ระดับ 35) ขึ้นไป แอปจะวาดใต้แถบระบบและแสดงส่วนที่ถูกตัดออกเมื่อแอปกำหนดเป้าหมายเป็น SDK 35 ซึ่งส่งผลให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ราบรื่นยิ่งขึ้น และช่วยให้แอปใช้ประโยชน์จากพื้นที่หน้าต่างที่มีได้อย่างเต็มที่

การแสดงเนื้อหาหลัง UI ของระบบเรียกว่าการใช้งานแบบไร้ขอบ ในหน้านี้ คุณจะได้เรียนรู้เกี่ยวกับส่วนตัดต่างๆ วิธีแสดงผลแบบเต็มหน้าจอ และวิธีใช้ API ส่วนตัดเพื่อแสดงภาพ UI แบบเคลื่อนไหวและตรวจสอบว่าเนื้อหาของแอปไม่ถูกบดบังโดยองค์ประกอบ UI ของระบบ

ข้อมูลพื้นฐานเกี่ยวกับส่วนแทรก

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

ขนาดของ UI ของระบบและข้อมูลเกี่ยวกับตําแหน่งที่จะวาง UI นั้นระบุผ่านส่วนเกิน

ส่วนต่างๆ ของ UI ของระบบจะมีส่วนแทรกประเภทที่เกี่ยวข้องซึ่งอธิบายขนาดและตำแหน่งของส่วนนั้น ตัวอย่างเช่น ข้อมูลแทรกของแถบสถานะจะระบุขนาดและตําแหน่งของแถบสถานะ ส่วนข้อมูลแทรกของแถบนําทางจะระบุขนาดและตําแหน่งของแถบนําทาง ชิ้นส่วนแต่ละประเภทประกอบด้วยขนาด 4 พิกเซล คือ ด้านบน ซ้าย ขวา และด้านล่าง มิติข้อมูลเหล่านี้ระบุระยะที่ UI ของระบบยื่นออกมาจากด้านที่เกี่ยวข้องของหน้าต่างแอป ดังนั้น UI ของแอปจึงต้องฝังเข้าไปเป็นจำนวนดังกล่าวเพื่อไม่ให้ทับซ้อนกับ UI ของระบบประเภทนั้น

ประเภทอินเซ็ตในตัวของ Android ที่พร้อมใช้งานผ่าน WindowInsets มีดังนี้

WindowInsets.statusBars

ส่วนที่เป็นภาพประกอบที่อธิบายแถบสถานะ แถบ UI ที่ด้านบนของระบบมีไอคอนการแจ้งเตือนและสัญญาณบอกสถานะอื่นๆ

WindowInsets.statusBarsIgnoringVisibility

แถบสถานะจะฝังอยู่เมื่อมองเห็น หากขณะนี้แถบสถานะซ่อนอยู่ (เนื่องจากเข้าสู่โหมดเต็มหน้าจอแบบสมจริง) ส่วนแทรกของแถบสถานะหลักจะว่างเปล่า แต่ส่วนแทรกเหล่านี้จะไม่ว่างเปล่า

WindowInsets.navigationBars

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

WindowInsets.navigationBarsIgnoringVisibility

แถบนําทางจะตั้งค่าไว้สําหรับเวลาที่ชิ้นงานเหล่านั้นปรากฏ หากขณะนี้แถบนำทางซ่อนอยู่ (เนื่องจากเข้าสู่โหมดเต็มหน้าจอที่สมจริง) ส่วนแทรกของแถบนำทางหลักจะว่างเปล่า แต่ส่วนแทรกเหล่านี้จะไม่ว่างเปล่า

WindowInsets.captionBar

ส่วนที่เป็นภาพแทรกที่อธิบายการตกแต่งหน้าต่าง UI ของระบบหากอยู่ในหน้าต่างรูปแบบอิสระ เช่น แถบชื่อด้านบน

WindowInsets.captionBarIgnoringVisibility

แถบคำบรรยายจะฝังอยู่เมื่อปรากฏ หากตอนนี้แถบคำบรรยายแทนเสียงซ่อนอยู่ ส่วนย่อยของแถบคำบรรยายแทนเสียงหลักจะว่างเปล่า แต่ส่วนย่อยเหล่านี้จะไม่ว่างเปล่า

WindowInsets.systemBars

การรวมส่วนแทรกของแถบระบบ ซึ่งรวมถึงแถบสถานะ แถบนําทาง และแถบคำบรรยายแทนเสียง

WindowInsets.systemBarsIgnoringVisibility

แถบระบบจะล้อมรอบช่วงเวลาที่มองเห็นได้ หากตอนนี้แถบระบบซ่อนอยู่ (เนื่องจากเข้าสู่โหมดเต็มหน้าจอแบบสมจริง) ส่วนตัดของแถบระบบหลักจะว่างเปล่า แต่ส่วนตัดเหล่านี้จะไม่ว่างเปล่า

WindowInsets.ime

ส่วนแทรกที่อธิบายจำนวนพื้นที่ด้านล่างของซอฟต์แวร์แป้นพิมพ์

WindowInsets.imeAnimationSource

ส่วนแทรกที่อธิบายปริมาณพื้นที่ที่ซอฟต์แวร์แป้นพิมพ์ใช้งานก่อนภาพเคลื่อนไหวของแป้นพิมพ์ปัจจุบัน

WindowInsets.imeAnimationTarget

ส่วนที่เป็นภาพแทรกที่อธิบายพื้นที่ที่แป้นพิมพ์ซอฟต์แวร์จะครอบครองหลังจากภาพเคลื่อนไหวของแป้นพิมพ์ปัจจุบัน

WindowInsets.tappableElement

ประเภทของส่วนที่อธิบายข้อมูลโดยละเอียดเพิ่มเติมเกี่ยวกับ UI การนำทาง โดยให้พื้นที่ที่ระบบจะจัดการ "การแตะ" ไม่ใช่แอป สำหรับแถบนำทางแบบโปร่งใสที่มีการนำทางด้วยท่าทางสัมผัส คุณสามารถแตะองค์ประกอบแอปบางอย่างผ่าน UI การนำทางของระบบได้

WindowInsets.tappableElementIgnoringVisibility

องค์ประกอบที่แตะได้จะฝังอยู่เมื่อมองเห็น หากองค์ประกอบที่แตะได้ซ่อนอยู่ (เนื่องจากกำลังเข้าสู่โหมดเต็มหน้าจอที่แตะได้) ส่วนองค์ประกอบหลักที่แตะได้จะว่างเปล่า แต่ส่วนเหล่านี้จะไม่ว่างเปล่า

WindowInsets.systemGestures

ส่วนที่ยื่นออกมาแสดงจำนวนส่วนที่ยื่นออกมาที่ระบบจะขัดจังหวะท่าทางสัมผัสสำหรับการไปยังส่วนต่างๆ แอปสามารถระบุการจัดการท่าทางสัมผัสเหล่านี้ในจำนวนที่จำกัดด้วยตนเองผ่าน Modifier.systemGestureExclusion

WindowInsets.mandatorySystemGestures

ท่าทางสัมผัสของระบบชุดย่อยที่ระบบจะจัดการเสมอ และเลือกไม่ใช้ผ่าน Modifier.systemGestureExclusion ไม่ได้

WindowInsets.displayCutout

ส่วนที่เป็นขอบแสดงถึงระยะห่างที่จําเป็นเพื่อหลีกเลี่ยงการซ้อนทับกับรอยบากของจอแสดงผล (รอยบากหรือรูเล็ก ๆ)

WindowInsets.waterfall

ส่วนที่เป็นภาพตัดแสดงพื้นที่โค้งของการแสดงผล Waterfall การแสดงผล Waterfall จะมีพื้นที่โค้งตามขอบของหน้าจอที่หน้าจอเริ่มโค้งไปตามด้านข้างของอุปกรณ์

ประเภทเหล่านี้จะสรุปเป็นประเภทโฆษณาซ้อนใน "ปลอดภัย" 3 ประเภทต่อไปนี้เพื่อให้มั่นใจว่าเนื้อหาจะไม่ถูกบดบัง

ประเภทเนื้อหาที่ฝัง "ปลอดภัย" เหล่านี้จะปกป้องเนื้อหาด้วยวิธีต่างๆ โดยอิงตามเนื้อหาที่ฝังของแพลตฟอร์มพื้นฐาน ดังนี้

  • ใช้ WindowInsets.safeDrawing เพื่อปกป้องเนื้อหาที่ไม่ควรวาดไว้ใต้ UI ของระบบ การใช้งานที่พบบ่อยที่สุดของส่วนตัดคือเพื่อป้องกันไม่ให้วาดเนื้อหาที่ถูกบดบังโดย UI ของระบบ (บางส่วนหรือทั้งหมด)
  • ใช้ WindowInsets.safeGestures เพื่อปกป้องเนื้อหาด้วยท่าทางสัมผัส วิธีนี้จะช่วยป้องกันไม่ให้ท่าทางสัมผัสของระบบขัดแย้งกับท่าทางสัมผัสของแอป (เช่น ท่าทางสัมผัสสำหรับชีตด้านล่าง ภาพสไลด์ หรือในเกม)
  • ใช้ WindowInsets.safeContent ร่วมกับ WindowInsets.safeDrawing และ WindowInsets.safeGestures เพื่อให้เนื้อหาไม่มีภาพซ้อนทับและท่าทางสัมผัสซ้อนทับ

การตั้งค่าส่วนแทรก

หากต้องการให้แอปควบคุมตําแหน่งที่จะวาดเนื้อหาได้อย่างเต็มที่ ให้ทําตามขั้นตอนการตั้งค่าต่อไปนี้ หากไม่ทำตามขั้นตอนเหล่านี้ แอปอาจวาดสีดำหรือสีพื้นหลัง UI ของระบบ หรือภาพเคลื่อนไหวไม่ซิงค์กับแป้นพิมพ์ซอฟต์แวร์

  1. กําหนดเป้าหมายเป็น SDK 35 ขึ้นไปเพื่อบังคับใช้การแสดงผลแบบไร้ขอบใน Android 15 ขึ้นไป แอปจะแสดงอยู่หลัง UI ของระบบ คุณสามารถปรับ UI ของแอปได้โดย จัดการสิ่งที่กำหนดค่าไว้
  2. (ไม่บังคับ) เรียกใช้ enableEdgeToEdge() ใน Activity.onCreate() ซึ่งจะช่วยให้แอปเป็นแบบไร้ขอบใน Android เวอร์ชันเก่าได้
  3. ตั้งค่า android:windowSoftInputMode="adjustResize" ในรายการ AndroidManifest.xml ของกิจกรรม การตั้งค่านี้ช่วยให้แอปได้รับขนาดของ IME ซอฟต์แวร์เป็นอินเซ็ต ซึ่งคุณใช้เพื่อเพิ่มระยะห่างและวางเลย์เอาต์เนื้อหาได้อย่างเหมาะสมเมื่อ IME ปรากฏและหายไปในแอป

    <!-- in your AndroidManifest.xml file: -->
    <activity
      android:name=".ui.MainActivity"
      android:label="@string/app_name"
      android:windowSoftInputMode="adjustResize"
      android:theme="@style/Theme.MyApplication"
      android:exported="true">
    

Compose APIs

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

ตัวอย่างเช่น นี่เป็นวิธีที่พื้นฐานที่สุดในการใช้ชิ้นส่วนกับเนื้อหาของทั้งแอป

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

    enableEdgeToEdge()

    setContent {
        Box(Modifier.safeDrawingPadding()) {
            // the rest of the app
        }
    }
}

ข้อมูลโค้ดนี้จะใช้ส่วนที่แทรกหน้าต่าง safeDrawing เป็นระยะห่างจากขอบรอบๆ เนื้อหาทั้งหมดของแอป แม้ว่าวิธีนี้จะดูแลให้องค์ประกอบแบบอินเทอร์แอกทีฟไม่ซ้อนทับกับ UI ของระบบ และหมายความว่าจะไม่มีแอปใดแสดงหลัง UI ของระบบเพื่อให้เกิดเอฟเฟกต์แบบขอบต่อขอบ หากต้องการใช้พื้นที่ในหน้าต่างทั้งหมด คุณจะต้องปรับแต่งตําแหน่งที่จะใช้ส่วนแทรกในแต่ละหน้าจอหรือคอมโพเนนต์

ข้อความที่แทรกประเภทเหล่านี้ทั้งหมดจะมีภาพเคลื่อนไหวโดยอัตโนมัติด้วยภาพเคลื่อนไหว IME ที่พอร์ตไปยัง API 21 นอกจากนี้ เลย์เอาต์ทั้งหมดที่ใช้ส่วนแทรกเหล่านี้จะเคลื่อนไหวโดยอัตโนมัติเมื่อค่าส่วนแทรกเปลี่ยนแปลง

การใช้ประเภทการฝังเหล่านี้เพื่อปรับเลย์เอาต์ Composable มี 2 วิธีหลักๆ ได้แก่ ตัวแก้ไขระยะห่างจากขอบและตัวแก้ไขขนาดการฝัง

ตัวปรับระยะห่างจากขอบ

Modifier.windowInsetsPadding(windowInsets: WindowInsets) ใช้ระยะขอบหน้าต่างที่ระบุเป็นระยะห่างจากขอบ โดยทํางานเหมือนกับที่ Modifier.padding จะทำ เช่น Modifier.windowInsetsPadding(WindowInsets.safeDrawing) ใช้ส่วนแทรกของภาพวาดที่ปลอดภัยเป็นระยะห่างจากขอบทั้ง 4 ด้าน

นอกจากนี้ยังมีวิธียูทิลิตีที่มีอยู่มากมายสำหรับประเภทด้านที่ใช้กันมากที่สุด Modifier.safeDrawingPadding() เป็นวิธีการหนึ่งซึ่งเทียบเท่ากับ Modifier.windowInsetsPadding(WindowInsets.safeDrawing) มีตัวแก้ไขที่คล้ายกัน สำหรับชิ้นงานประเภทอื่นๆ

ตัวแก้ไขขนาดของส่วนแทรก

ตัวแก้ไขต่อไปนี้จะใช้จำนวนส่วนโค้งมนของหน้าต่างโดยกำหนดขนาดของคอมโพเนนต์ให้เท่ากับขนาดของส่วนโค้งมน

Modifier.windowInsetsStartWidth(windowInsets: WindowInsets)

ใช้ด้านเริ่มต้นของ windowInsets เป็นความกว้าง (เช่น Modifier.width)

Modifier.windowInsetsEndWidth(windowInsets: WindowInsets)

ใช้ด้านท้ายของ windowInsets เป็นความกว้าง (เช่น Modifier.width)

Modifier.windowInsetsTopHeight(windowInsets: WindowInsets)

ใช้ด้านข้างบนของ windowInsets เป็นค่าความสูง (เช่น Modifier.height)

Modifier.windowInsetsBottomHeight(windowInsets: WindowInsets)

ใช้ด้านล่างของ windowInsets เป็นค่าความสูง (เช่น Modifier.height)

ตัวแก้ไขเหล่านี้มีประโยชน์อย่างยิ่งสำหรับการปรับขนาด Spacer ที่กินพื้นที่ของส่วนแทรก

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

การใช้งานที่ฝัง

ตัวแก้ไขระยะห่างจากขอบใน (windowInsetsPadding และตัวช่วย เช่น safeDrawingPadding) จะใช้ส่วนของส่วนที่เยื้องเข้ามาซึ่งใช้เป็นระยะห่างจากขอบโดยอัตโนมัติ เมื่อเข้าไปในลําดับชั้นองค์ประกอบที่ลึกขึ้น ตัวแก้ไขระยะห่างจากขอบด้านในแบบฝังและตัวแก้ไขขนาดระยะห่างจากขอบด้านในจะทราบว่ามีการใช้ระยะห่างจากขอบด้านในบางส่วนโดยตัวแก้ไขระยะห่างจากขอบด้านในแบบด้านนอกแล้ว และหลีกเลี่ยงการใช้ระยะห่างจากขอบด้านในส่วนเดียวกันมากกว่า 1 ครั้ง ซึ่งจะทำให้มีช่องว่างเกินมาก

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

ด้วยเหตุนี้ ตัวแก้ไขการเว้นวรรคที่ฝังไว้จึงเปลี่ยนจำนวนการเว้นวรรคที่ใช้กับคอมโพสิเบิลแต่ละรายการโดยอัตโนมัติ

เมื่อดูตัวอย่าง LazyColumn เดียวกันกับก่อนหน้านี้ LazyColumn จะได้รับการปรับขนาดโดยตัวแก้ไข imePadding ภายใน LazyColumn รายการสุดท้ายจะปรับขนาดให้เท่ากับความสูงของด้านล่างของแถบระบบ

LazyColumn(
    Modifier.imePadding()
) {
    // Other content
    item {
        Spacer(
            Modifier.windowInsetsBottomHeight(
                WindowInsets.systemBars
            )
        )
    }
}

เมื่อ IME ปิดอยู่ ตัวแก้ไข imePadding() จะไม่ใช้ระยะห่างจากขอบ เนื่องจาก IME ไม่มีความสูง เนื่องจากตัวแก้ไข imePadding() ไม่ได้ใช้ระยะห่างจากขอบ ระบบจะไม่ใช้ส่วนเว้า และส่วนสูงของ Spacer จะเท่ากับขนาดของด้านด้านล่างของแถบระบบ

เมื่อ IME เปิดขึ้น ส่วน IME จะเคลื่อนไหวเพื่อให้ตรงกับขนาดของ IME และตัวแก้ไข imePadding() จะเริ่มใช้ระยะห่างจากขอบด้านล่างเพื่อปรับขนาด LazyColumn เมื่อ IME เปิดขึ้น เมื่อตัวแก้ไข imePadding() เริ่มใช้ระยะห่างจากขอบด้านล่าง ก็จะเริ่มใช้ระยะห่างจากขอบในจำนวนนั้นด้วย ดังนั้น ความสูงของ Spacer จึงเริ่มลดลง เนื่องจากตัวดัดแปลง imePadding() ได้ใช้การเว้นวรรคสำหรับแถบของระบบแล้ว เมื่อตัวแก้ไข imePadding() ใช้ระยะห่างจากขอบด้านล่างที่มากกว่าแถบระบบ ความสูงของ Spacer จะเท่ากับ 0

เมื่อ IME ปิดลง การเปลี่ยนแปลงจะเกิดขึ้นในลักษณะกลับกัน โดย Spacer จะเริ่มขยายจากระดับความสูง 0 เมื่อ imePadding() มีผลน้อยกว่าด้านด้านล่างของแถบระบบ จนในที่สุด Spacer จะเท่ากับความสูงของด้านด้านล่างของแถบระบบเมื่อ IME แสดงภาพเคลื่อนไหวจนหมด

รูปที่ 2 คอลัมน์แบบ Lazy จากขอบถึงขอบที่มี TextField

ลักษณะการทำงานนี้เกิดขึ้นจากการสื่อสารระหว่างตัวปรับเปลี่ยน windowInsetsPadding ทั้งหมด และอาจได้รับอิทธิพลจากวิธีอื่นๆ อีก 2 วิธี

นอกจากนี้ Modifier.consumeWindowInsets(insets: WindowInsets) ยังใช้ส่วนแทรกในลักษณะเดียวกับ Modifier.windowInsetsPadding แต่จะไม่ใช้ส่วนที่บริโภคเป็นระยะห่างจากขอบ ซึ่งจะเป็นประโยชน์เมื่อใช้ร่วมกับตัวปรับขนาดด้านการแสดง เพื่อบอกพี่น้องว่ามีการใช้ชุดย่อยไปแล้วจำนวนหนึ่งแล้ว ดังนี้

Column(Modifier.verticalScroll(rememberScrollState())) {
    Spacer(Modifier.windowInsetsTopHeight(WindowInsets.systemBars))

    Column(
        Modifier.consumeWindowInsets(
            WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
        )
    ) {
        // content
        Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
    }

    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}

Modifier.consumeWindowInsets(paddingValues: PaddingValues) ทำงานคล้ายกับเวอร์ชันที่มีอาร์กิวเมนต์ WindowInsets มาก แต่จะใช้ PaddingValues ที่กำหนดเอง วิธีนี้มีประโยชน์ในการแจ้งบุตรหลานเมื่อมีการให้ระยะห่างจากขอบหรือการเว้นระยะห่างจากกลไกบางอย่างที่ไม่ใช่ตัวแก้ไขระยะห่างจากขอบที่ประกอบไว้ เช่น Modifier.padding ปกติหรือการเว้นวรรคที่มีความสูงคงที่

Column(Modifier.padding(16.dp).consumeWindowInsets(PaddingValues(16.dp))) {
    // content
    Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.ime))
}

ในกรณีที่ต้องใช้การแทรกหน้าต่างดิบโดยไม่มีการใช้งาน ให้ใช้ค่า WindowInsets โดยตรง หรือใช้ WindowInsets.asPaddingValues() เพื่อแสดงผล PaddingValues ของชิ้นส่วนที่ไม่ได้รับผลกระทบจากการใช้งาน อย่างไรก็ตาม เนื่องจากข้อควรระวังด้านล่าง เราขอแนะนำให้ใช้ตัวแก้ไขระยะห่างจากขอบของส่วนแทรกของหน้าต่างและตัวแก้ไขขนาดของส่วนแทรกของหน้าต่างเมื่อเป็นไปได้

ระยะต่างๆ ของ Insets และ Jetpack Compose

Compose ใช้ API หลักของ AndroidX ที่อยู่เบื้องหลังเพื่ออัปเดตและแสดงภาพเคลื่อนไหวของส่วนตัด ซึ่งใช้ API ของแพลตฟอร์มที่อยู่เบื้องหลังซึ่งจัดการส่วนตัด ลักษณะการทํางานของแพลตฟอร์มนี้ทําให้ส่วนตัดสัมพันธ์กับระยะของ Jetpack Compose เป็นพิเศษ

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

ภาพเคลื่อนไหวของ IME บนแป้นพิมพ์ที่มี WindowInsets

คุณสามารถใช้ Modifier.imeNestedScroll() กับคอนเทนเนอร์แบบเลื่อนเพื่อเปิดและปิด IME โดยอัตโนมัติเมื่อเลื่อนไปที่ด้านล่างของคอนเทนเนอร์

class WindowInsetsExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
                MyScreen()
            }
        }
    }
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon(imageVector = Icons.Filled.Add, contentDescription = "Add")
        }
    }
}

ภาพเคลื่อนไหวแสดงองค์ประกอบ UI ที่เลื่อนขึ้นและลงเพื่อให้แป้นพิมพ์แสดง
รูปที่ 3 ภาพเคลื่อนไหวของ IME

การรองรับการฝังสำหรับคอมโพเนนต์ Material 3

Composable ที่มีมาในตัว Material 3 (androidx.compose.material3) จำนวนมากจะประกอบเข้าด้วยกันตามวิธีวาง Composable ในแอปตามข้อกำหนดของ Material เพื่อให้ใช้งานได้ง่าย

การจัดการคอมโพเนนต์ที่ประกอบกันได้ในอินเซ็ต

ด้านล่างนี้คือรายการคอมโพเนนต์ของ Material ที่จัดการส่วนเกินโดยอัตโนมัติ

แถบแอป

  • TopAppBar / SmallTopAppBar / CenterAlignedTopAppBar / MediumTopAppBar / LargeTopAppBar: ใช้ด้านด้านบนและแนวนอนของแถบระบบเป็นระยะห่างจากขอบ เนื่องจากแถบดังกล่าวใช้ที่ด้านบนของหน้าต่าง
  • BottomAppBar: ใช้ด้านด้านล่างและแนวนอนของแถบระบบเป็นระยะห่างจากขอบ

คอนเทนเนอร์เนื้อหา

  • ModalDrawerSheet / DismissibleDrawerSheet / PermanentDrawerSheet (เนื้อหาภายในลิ้นชักการนำทางแบบโมดอล): ใช้ส่วนแทรกแนวตั้งและเริ่มต้นกับเนื้อหา
  • ModalBottomSheet: ใช้ระยะล่าง
  • NavigationBar : ใช้การฝังด้านล่างและแนวนอน
  • NavigationRail: ใช้การเยื้องแนวตั้งและเริ่มต้น

Scaffold

โดยค่าเริ่มต้น Scaffold จะระบุข้อมูลประกอบเป็นพารามิเตอร์ paddingValues เพื่อให้คุณใช้งาน Scaffold ไม่ใช้ส่วนแทรกกับเนื้อหา ความรับผิดชอบนี้เป็นของคุณ ตัวอย่างเช่น หากต้องการใช้ส่วนตัดเหล่านี้ที่มี LazyColumn ภายใน Scaffold ให้ทำดังนี้

Scaffold { innerPadding ->
    // innerPadding contains inset information for you to use and apply
    LazyColumn(
        // consume insets as scaffold doesn't do it by default
        modifier = Modifier.consumeWindowInsets(innerPadding),
        contentPadding = innerPadding
    ) {
        items(count = 100) {
            Box(
                Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .background(colors[it % colors.size])
            )
        }
    }
}

ลบล้างระยะขอบเริ่มต้น

คุณเปลี่ยนพารามิเตอร์ windowInsets ที่ส่งผ่านไปยัง Composable เพื่อกำหนดค่าลักษณะการทำงานของ Composable ได้ พารามิเตอร์นี้อาจเป็นส่วนโค้งมนของหน้าต่างประเภทอื่นที่จะใช้แทน หรือปิดใช้โดยการส่งอินสแตนซ์ว่างเปล่า WindowInsets(0, 0, 0, 0)

ตัวอย่างเช่น หากต้องการปิดใช้การจัดการส่วนเกินใน LargeTopAppBar ให้ตั้งค่าพารามิเตอร์ windowInsets เป็นอินสแตนซ์ว่าง ดังนี้

LargeTopAppBar(
    windowInsets = WindowInsets(0, 0, 0, 0),
    title = {
        Text("Hi")
    }
)

การทำงานร่วมกันกับส่วนระบบของ View

คุณอาจต้องลบล้างส่วนเริ่มต้นเมื่อหน้าจอมีทั้งมุมมองและโค้ด Compose ในลำดับชั้นเดียวกัน ในกรณีนี้ คุณต้องระบุอย่างชัดเจนว่าชิ้นงานใดควรใช้ส่วนแทรก และชิ้นงานใดควรละเว้น

เช่น หากเลย์เอาต์ด้านนอกสุดคือเลย์เอาต์ View ของ Android คุณควรใช้ส่วนตัดในระบบ View และละเว้นส่วนตัดสำหรับ Compose หรือหากเลย์เอาต์ด้านนอกสุดเป็น Composable คุณควรใช้ส่วนประกอบใน Compose และใส่ Composable ของ AndroidView ตามนั้น

โดยค่าเริ่มต้น ComposeView แต่ละรายการจะใช้อินเซ็ตทั้งหมดในระดับการบริโภค WindowInsetsCompat หากต้องการเปลี่ยนลักษณะการทำงานเริ่มต้นนี้ ให้ตั้งค่า ComposeView.consumeWindowInsets เป็น false

การปกป้องแถบระบบ

เมื่อแอปกำหนดเป้าหมายเป็น SDK 35 ขึ้นไป ระบบจะบังคับใช้การแสดงผลแบบไร้ขอบ แถบสถานะของระบบและแถบการนำทางด้วยท่าทางสัมผัสจะโปร่งใส แต่แถบนำทางแบบ 3 ปุ่มจะโปร่งแสง

หากต้องการนำการป้องกันพื้นหลังการนำทางแบบ 3 ปุ่มโปร่งแสงเริ่มต้นออก ให้ตั้งค่า Window.setNavigationBarContrastEnforced เป็น false

แหล่งข้อมูล