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

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

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

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

สำหรับแอปที่อิงตาม View จะมี Material 3 เวอร์ชันให้ใช้งาน ได้แก่

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

สำหรับแอป Compose จะมี Material 2 เวอร์ชันให้ใช้งาน ได้แก่

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

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

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

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

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

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

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

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

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

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()
    }
}

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

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

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

การใช้ ViewModel ใน Compose

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

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

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

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 ด้วย Composable ที่เป็นปลายทางในกราฟการนำทางจึงมีอินสแตนซ์ของ ViewModel ที่แตกต่างกัน ในกรณีนี้ ViewModel จะอยู่ในขอบเขตของวงจรของปลายทาง และ จะล้างเมื่อนำปลายทางออกจาก Backstack ในตัวอย่างต่อไปนี้ เมื่อผู้ใช้ไปยังหน้าจอโปรไฟล์ ระบบจะสร้างอินสแตนซ์ใหม่ของ 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 มากที่สุด

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

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

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

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

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

ในตัวอย่างต่อไปนี้ CustomViewGroup มี TextView และ ComposeView ที่มี Composable 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 ที่กำหนดเอง คุณอาจต้องใช้ทั้งในหน้าจอที่อิงตาม Compose และ View

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

หากต้องการใช้ Composable ในหน้าจอที่อิงตาม View ให้สร้าง View Wrapper ที่กำหนดเองซึ่ง ขยายจาก AbstractComposeView ใน ContentComposable ที่ลบล้าง ให้วาง 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 ช่วยให้แสดง Composable ที่แตกต่างกันโดยสิ้นเชิงได้ง่ายๆ โดยใช้ตรรกะแบบมีเงื่อนไขใน Kotlin ดังนี้

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

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

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

ส่งเสริมคอมโพเนนต์ที่แคปซูลและนำกลับมาใช้ใหม่ได้

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

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

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

@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 แบบปรับเปลี่ยนได้

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

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

เขียนใน RecyclerView

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

WindowInsets การทำงานร่วมกันกับ Views

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

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

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

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