ข้อควรพิจารณาอื่นๆ

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

การย้ายข้อมูลธีมของแอป

การออกแบบ Material Design เป็นระบบการออกแบบที่แนะนำสำหรับธีมแอป Android

สำหรับแอปที่ทำงานแบบมุมมอง คุณจะเลือกใช้ Material ได้ 3 เวอร์ชันดังนี้

  • Material Design 1 โดยใช้ไลบรารี AppCompat (เช่น Theme.AppCompat.*)
  • Material Design 2 โดยใช้ไลบรารี MDC-Android (เช่น Theme.MaterialComponents.*)
  • Material Design 3 โดยใช้ไลบรารี MDC-Android (เช่น Theme.Material3.*)

สำหรับแอป Compose วัสดุมี 2 เวอร์ชัน ได้แก่

  • Material Design 2 โดยใช้ไลบรารี Compose Material (เช่น androidx.compose.material.MaterialTheme)
  • การออกแบบ Material 3 โดยใช้ไลบรารี Compose Material 3 (เช่น androidx.compose.material3.MaterialTheme)

เราขอแนะนำให้ใช้เวอร์ชันล่าสุด (Material 3) หากระบบการออกแบบของแอปรองรับการใช้งานเวอร์ชันดังกล่าว เรามีคำแนะนำในการย้ายข้อมูลสำหรับทั้ง Views และ Compose ดังนี้

เมื่อสร้างหน้าจอใหม่ใน Compose ไม่ว่าคุณจะใช้ Material Design เวอร์ชันใดก็ตาม โปรดตรวจสอบว่าคุณใช้ MaterialTheme ก่อนคอมโพสิเบิลที่แสดง UI จากคลัง Material ของ Compose คอมโพเนนต์ของวัสดุ (Button, Text ฯลฯ) ขึ้นอยู่กับว่ามี MaterialTheme อยู่หรือไม่ และลักษณะการทํางานจะไม่มีการกําหนดหากไม่มี MaterialTheme

ตัวอย่าง Jetpack Compose ทั้งหมดใช้ธีม Compose ที่กําหนดเองซึ่งสร้างขึ้นจาก MaterialTheme

ดูข้อมูลเพิ่มเติมที่ระบบการออกแบบใน Compose และการย้ายธีม XML ไปยัง Compose

หากคุณใช้คอมโพเนนต์การนำทางในแอป โปรดดูข้อมูลเพิ่มเติมในการไปยังส่วนต่างๆ ด้วย Compose - การทํางานร่วมกัน และย้ายข้อมูลการนำทางของ Jetpack ไปยัง Navigation Compose

ทดสอบ UI ของ Compose/Views แบบผสม

หลังจากย้ายข้อมูลบางส่วนของแอปไปยัง Compose แล้ว การทดสอบเป็นขั้นตอนสําคัญเพื่อให้แน่ใจว่าคุณไม่ได้ทําให้แอปเสียหาย

เมื่อกิจกรรมหรือแฟรกเมนต์ใช้ Compose คุณต้องใช้ createAndroidComposeRule แทนการใช้ ActivityScenarioRule createAndroidComposeRule ผสานรวม ActivityScenarioRule กับ ComposeTestRule ซึ่งช่วยให้คุณทดสอบการเขียนโค้ดและดูโค้ดได้พร้อมกัน

class MyActivityTest {
    @Rule
    @JvmField
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Test
    fun testGreeting() {
        val greeting = InstrumentationRegistry.getInstrumentation()
            .targetContext.resources.getString(R.string.greeting)

        composeTestRule.onNodeWithText(greeting).assertIsDisplayed()
    }
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบได้ที่การทดสอบเลย์เอาต์การเขียน ดูข้อมูลเกี่ยวกับการทำงานร่วมกันกับเฟรมเวิร์กการทดสอบ UI ได้ที่การทำงานร่วมกันกับ Espresso และการทำงานร่วมกันกับ UiAutomator

การผสานรวม Compose เข้ากับสถาปัตยกรรมแอปที่มีอยู่

รูปแบบสถาปัตยกรรม Unidirectional Data Flow (UDF) ทำงานร่วมกับ Compose ได้อย่างราบรื่น หากแอปใช้รูปแบบสถาปัตยกรรมประเภทอื่นๆ แทน เช่น Model View Presenter (MVP) เราขอแนะนำให้คุณย้ายข้อมูล UI ในส่วนนั้นไปยัง UDF ก่อนหรือขณะที่ใช้งาน Compose

การใช้ ViewModel ในเครื่องมือเขียน

หากใช้ไลบรารี Architecture Components ViewModel คุณจะเข้าถึง ViewModel จากคอมโพสิเบิลใดก็ได้โดยเรียกใช้ฟังก์ชัน viewModel() ตามที่อธิบายไว้ในCompose และไลบรารีอื่นๆ

เมื่อใช้ Compose โปรดระมัดระวังในการใช้ ViewModel ประเภทเดียวกันในคอมโพสิเบิลต่างๆ เนื่องจากองค์ประกอบ ViewModel จะเป็นไปตามขอบเขตวงจรชีวิตของ View ขอบเขตจะเป็นกิจกรรมของโฮสต์ เศษ หรือกราฟการนําทาง หากใช้คลังการนําทาง

เช่น หากคอมโพสิเบิลโฮสต์อยู่ในกิจกรรม viewModel() จะแสดงอินสแตนซ์เดียวกันเสมอ ซึ่งระบบจะล้างออกก็ต่อเมื่อกิจกรรมเสร็จสิ้น ในตัวอย่างต่อไปนี้ ระบบจะทักทายผู้ใช้รายเดียวกัน ("user1") 2 ครั้ง เนื่องจากมีการใช้อินสแตนซ์ GreetingViewModel เดียวกันซ้ำในคอมโพสิเบิลทั้งหมดภายใต้กิจกรรมของโฮสต์ ระบบจะนำอินสแตนซ์ ViewModel แรกที่สร้างมาใช้ซ้ำในคอมโพสิเบิลอื่นๆ

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

        setContent {
            MaterialTheme {
                Column {
                    GreetingScreen("user1")
                    GreetingScreen("user2")
                }
            }
        }
    }
}

@Composable
fun GreetingScreen(
    userId: String,
    viewModel: GreetingViewModel = viewModel(  
        factory = GreetingViewModelFactory(userId)  
    )
) {
    val messageUser by viewModel.message.observeAsState("")
    Text(messageUser)
}

class GreetingViewModel(private val userId: String) : ViewModel() {
    private val _message = MutableLiveData("Hi $userId")
    val message: LiveData<String> = _message
}

class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return GreetingViewModel(userId) as T
    }
}

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

@Composable
fun MyApp() {
    NavHost(rememberNavController(), startDestination = "profile/{userId}") {
        /* ... */
        composable("profile/{userId}") { backStackEntry ->
            GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "")
        }
    }
}

แหล่งข้อมูลที่เป็นความจริงของรัฐ

เมื่อคุณใช้ Compose ในส่วนหนึ่งของ UI อาจมีความจำเป็นที่ Compose และรหัสระบบของ View จะต้องแชร์ข้อมูล เราขอแนะนําให้คุณรวมสถานะที่แชร์นั้นไว้ในคลาสอื่นที่เป็นไปตามแนวทางปฏิบัติแนะนําของ UDF ซึ่งทั้ง 2 แพลตฟอร์มใช้ เช่น ใน ViewModel ที่แสดงสตรีมข้อมูลที่แชร์เพื่อส่งการอัปเดตข้อมูล

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

เขียนเป็นแหล่งข้อมูลที่เชื่อถือได้

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

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

@Composable
fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics {
    val analytics: FirebaseAnalytics = remember {
        FirebaseAnalytics()
    }

    // On every successful composition, update FirebaseAnalytics with
    // the userType from the current User, ensuring that future analytics
    // events have this metadata attached
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }
    return analytics
}

ดูข้อมูลเพิ่มเติมได้ที่ผลข้างเคียงใน Compose

ดูระบบเป็นแหล่งข้อมูลที่ถูกต้อง

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

ในตัวอย่างต่อไปนี้ CustomViewGroup มี TextView และ ComposeView ที่มี TextField อยู่ภายใน TextView ต้องแสดงเนื้อหาของสิ่งที่ผู้ใช้พิมพ์ใน TextField

class CustomViewGroup @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle) {

    // Source of truth in the View system as mutableStateOf
    // to make it thread-safe for Compose
    private var text by mutableStateOf("")

    private val textView: TextView

    init {
        orientation = VERTICAL

        textView = TextView(context)
        val composeView = ComposeView(context).apply {
            setContent {
                MaterialTheme {
                    TextField(value = text, onValueChange = { updateState(it) })
                }
            }
        }

        addView(textView)
        addView(composeView)
    }

    // Update both the source of truth and the TextView
    private fun updateState(newValue: String) {
        text = newValue
        textView.text = newValue
    }
}

การย้ายข้อมูล UI ที่แชร์

หากจะค่อยๆ ย้ายข้อมูลไปยัง Compose คุณอาจต้องใช้องค์ประกอบ UI ที่แชร์ทั้งใน Compose และระบบ View เช่น หากแอปของคุณมีคอมโพเนนต์ CallToActionButton ที่กําหนดเอง คุณอาจต้องใช้คอมโพเนนต์นั้นทั้งในหน้าจอแบบคอมโพสิทและแบบ View

ใน Compose องค์ประกอบ UI ที่แชร์จะกลายเป็นคอมโพสิเบิลที่นํากลับมาใช้ใหม่ได้ทั่วทั้งแอป ไม่ว่าองค์ประกอบนั้นจะจัดสไตล์โดยใช้ XML หรือเป็นมุมมองที่กําหนดเองก็ตาม เช่น คุณอาจสร้างคอมโพสิเบิล CallToActionButton สําหรับคอมโพเนนต์คำกระตุ้นให้ดำเนินการ Button ที่กําหนดเอง

หากต้องการใช้คอมโพสิเบิลในหน้าจอแบบ View ให้สร้าง Wrapper มุมมองที่กําหนดเองซึ่งขยายจาก AbstractComposeView ใน Composable Content ที่ลบล้าง ให้วาง Composable ที่คุณสร้างขึ้นซึ่งรวมอยู่ในธีม Compose ดังที่แสดงในตัวอย่างด้านล่าง

@Composable
fun CallToActionButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            containerColor = MaterialTheme.colorScheme.secondary
        ),
        onClick = onClick,
        modifier = modifier,
    ) {
        Text(text)
    }
}

class CallToActionViewButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {

    var text by mutableStateOf("")
    var onClick by mutableStateOf({})

    @Composable
    override fun Content() {
        YourAppTheme {
            CallToActionButton(text, onClick)
        }
    }
}

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

class ViewBindingActivity : ComponentActivity() {

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.callToAction.apply {
            text = getString(R.string.greeting)
            onClick = { /* Do something */ }
        }
    }
}

หากคอมโพเนนต์ที่กําหนดเองมีสถานะที่เปลี่ยนแปลงได้ โปรดดูแหล่งที่มาของความจริงของสถานะ

ให้ความสำคัญกับการแยกสถานะออกจากงานนำเสนอ

เดิมที View จะเป็นแบบมีสถานะ View จะจัดการช่องที่อธิบายสิ่งที่จะแสดง นอกเหนือจากวิธีแสดง เมื่อแปลง View เป็น Compose ให้แยกข้อมูลที่แสดงผลเพื่อให้ได้การไหลของข้อมูลแบบทิศทางเดียว ตามที่อธิบายไว้เพิ่มเติมในการยกสถานะ

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

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

@Composable
fun MyComposable(showCautionIcon: Boolean) {
    if (showCautionIcon) {
        CautionIcon(/* ... */)
    }
}

CautionIcon ไม่จำเป็นต้องรู้หรือสนใจว่าเหตุใดจึงมีการแสดง CautionIcon ขึ้น และไม่มีแนวคิดของ visibility นั่นคือ CautionIcon อยู่ในองค์ประกอบหรือไม่อยู่ในองค์ประกอบ

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

โปรโมตคอมโพเนนต์ที่แยกออกมาและนํามาใช้ซ้ำได้

องค์ประกอบ View มักจะทราบตำแหน่งที่ตัวเองอยู่ เช่น ภายใน Activity, Dialog, Fragment หรือภายในลําดับชั้น View อื่น เนื่องจากไฟล์เหล่านี้มักสร้างขึ้นจากไฟล์เลย์เอาต์แบบคงที่ โครงสร้างโดยรวมของ View จึงมีแนวโน้มที่จะเป็นแบบแผนมาก ซึ่งส่งผลให้การเชื่อมโยงแน่นขึ้นและทำให้Viewเปลี่ยนแปลงหรือนํามาใช้ซ้ำได้ยากขึ้น

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

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

@Composable
fun AScreen() {
    var isEnabled by rememberSaveable { mutableStateOf(false) }

    Column {
        ImageWithEnabledOverlay(isEnabled)
        ControlPanelWithToggle(
            isEnabled = isEnabled,
            onEnabledChanged = { isEnabled = it }
        )
    }
}

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

  • ImageWithEnabledOverlay เพียงต้องทราบว่าสถานะปัจจุบันของ isEnabled เป็นอย่างไร ไม่จำเป็นต้องทราบว่า ControlPanelWithToggle มีอยู่ หรือแม้แต่วิธีควบคุม

  • ControlPanelWithToggle ไม่รู้ว่ามี ImageWithEnabledOverlay อยู่ isEnabled อาจแสดงด้วยวิธีใดวิธีหนึ่งหรือมากกว่านั้น และ ControlPanelWithToggle ไม่จำเป็นต้องเปลี่ยนแปลง

  • ImageWithEnabledOverlay หรือ ControlPanelWithToggle จะฝังลึกแค่ไหน ก็ไม่มีผลต่อองค์ประกอบหลัก เด็กเหล่านั้นอาจกำลังแสดงภาพเคลื่อนไหวการเปลี่ยนแปลง เปลี่ยนเนื้อหา หรือส่งต่อเนื้อหาให้เด็กคนอื่นๆ

รูปแบบนี้เรียกว่าการกลับการควบคุม ซึ่งคุณสามารถอ่านข้อมูลเพิ่มเติมได้ในเอกสารประกอบของ CompositionLocal

การจัดการกับการเปลี่ยนแปลงขนาดหน้าจอ

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

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

การเลื่อนที่ฝังไว้ด้วย Views

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

เขียนใน RecyclerView

คอมโพสิเบิลใน RecyclerView มีประสิทธิภาพตั้งแต่ RecyclerView เวอร์ชัน 1.3.0-alpha02 โปรดตรวจสอบว่าคุณใช้ RecyclerView เวอร์ชัน 1.3.0-alpha02 เป็นอย่างน้อยจึงจะเห็นสิทธิประโยชน์เหล่านั้น

WindowInsets ทำงานร่วมกับข้อมูลพร็อพเพอร์ตี้

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

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

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

อ่านข้อมูลเพิ่มเติมได้ที่เอกสารประกอบเกี่ยวกับ WindowInsets ใน Compose