แม้ว่าการย้ายข้อมูลจาก 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
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- แสดงอีโมจิ
- Material Design 2 ในเครื่องมือเขียน
- ส่วนที่เว้นไว้ในหน้าต่างในเครื่องมือเขียน