Jetpack Compose สร้างขึ้นจาก Kotlin ในบางกรณี Kotlin มีนิพจน์พิเศษที่ช่วยให้เขียนโค้ด Compose ที่ดีได้ง่ายขึ้น หากคุณคิดเป็นภาษาโปรแกรมอื่นและแปลภาษานั้นเป็น Kotlin ในใจ คุณอาจพลาดความสามารถบางอย่างของ Compose และอาจเข้าใจโค้ด Kotlin ที่เขียนตามแบบแผนได้ยาก เพิ่มขึ้น ความคุ้นเคยกับสไตล์ของ Kotlin จะช่วยให้คุณหลีกเลี่ยงข้อผิดพลาดเหล่านั้นได้
อาร์กิวเมนต์เริ่มต้น
เมื่อเขียนฟังก์ชัน Kotlin คุณสามารถระบุค่าเริ่มต้นสำหรับอาร์กิวเมนต์ของฟังก์ชัน ซึ่งจะใช้ในกรณีที่ผู้เรียกใช้ไม่ได้ส่งค่าเหล่านั้นอย่างชัดเจน ฟีเจอร์นี้ช่วยลดความจำเป็นในการใช้ฟังก์ชันที่โอเวอร์โหลด
เช่น สมมติว่าคุณต้องการเขียนฟังก์ชันที่วาดรูปสี่เหลี่ยมจัตุรัส นั่น อาจมีพารามิเตอร์เดียวที่จำเป็นคือ sideLength ซึ่งระบุความยาว ของแต่ละด้าน ฟังก์ชันนี้อาจมีพารามิเตอร์ที่ไม่บังคับหลายรายการ เช่น thickness, edgeColor และอื่นๆ หากผู้เรียกใช้ไม่ได้ระบุพารามิเตอร์เหล่านั้น ฟังก์ชันจะใช้ค่าเริ่มต้น สำหรับภาษาอื่น คุณอาจเขียนว่า หลายฟังก์ชัน ได้แก่
// We don't need to do this in Kotlin! void drawSquare(int sideLength) { } void drawSquare(int sideLength, int thickness) { } void drawSquare(int sideLength, int thickness, Color edgeColor) { }
ใน Kotlin คุณจะเขียนฟังก์ชันเดียวและระบุค่าเริ่มต้นสําหรับ อาร์กิวเมนต์ดังนี้
fun drawSquare( sideLength: Int, thickness: Int = 2, edgeColor: Color = Color.Black ) { }
นอกจากจะช่วยให้คุณไม่ต้องเขียนฟังก์ชันซ้ำซ้อนหลายรายการแล้ว ฟีเจอร์นี้ยังช่วยให้โค้ดอ่านง่ายขึ้นมาก หากผู้เรียกใช้ไม่ได้ระบุค่าสำหรับอาร์กิวเมนต์ แสดงว่าผู้เรียกใช้ยินดีที่จะใช้ค่าเริ่มต้น นอกจากนี้ พารามิเตอร์ที่มีชื่อยังทำให้
คุณเห็นว่ามีอะไรเกิดขึ้นบ้าง
เปิดอยู่ หากดูโค้ดและเห็นการเรียกใช้ฟังก์ชันเช่นนี้ คุณอาจไม่ทราบว่าพารามิเตอร์มีความหมายอย่างไรหากไม่ตรวจสอบโค้ด drawSquare()
drawSquare(30, 5, Color.Red);
ในทางตรงกันข้าม โค้ดนี้เป็นการบันทึกด้วยตนเอง:
drawSquare(sideLength = 30, thickness = 5, edgeColor = Color.Red)
ไลบรารี Compose ส่วนใหญ่ใช้อาร์กิวเมนต์เริ่มต้น และคุณควรใช้อาร์กิวเมนต์เริ่มต้นกับฟังก์ชันคอมโพสิเบิลที่คุณเขียนด้วย แนวทางนี้ช่วยให้คอมโพสิเบิลปรับแต่งได้ แต่ยังคงเรียกใช้ลักษณะการทำงานเริ่มต้นได้ง่ายๆ ตัวอย่างเช่น คุณอาจสร้างองค์ประกอบข้อความง่ายๆ ดังนี้
Text(text = "Hello, Android!")
โค้ดดังกล่าวจะให้ผลเช่นเดียวกับโค้ดต่อไปนี้ ซึ่งมีรายละเอียดที่ละเอียดยิ่งขึ้น
ของ
Text
ตั้งค่าพารามิเตอร์ไว้อย่างชัดแจ้ง ดังนี้
Text( text = "Hello, Android!", color = Color.Unspecified, fontSize = TextUnit.Unspecified, letterSpacing = TextUnit.Unspecified, overflow = TextOverflow.Clip )
ซึ่งไม่เพียงเป็นข้อมูลโค้ดชิ้นแรกที่อ่านง่ายขึ้นและยังอ่านง่ายขึ้น
การทำเอกสารด้วยตนเอง การระบุเฉพาะพารามิเตอร์ text
หมายความว่าคุณบันทึกว่า
พารามิเตอร์อื่นๆ ทั้งหมด คุณควรเลือกใช้ค่าเริ่มต้น ในทางตรงกันข้าม
ตัวอย่างที่สองแสดงว่าคุณต้องการกำหนดค่าสำหรับส่วนขยายเหล่านั้น
พารามิเตอร์อื่นๆ แม้ว่าค่าที่คุณตั้งจะเป็นค่าเริ่มต้นสำหรับ
ฟังก์ชัน
ฟังก์ชันระดับสูงและนิพจน์ LAMBDA
Kotlin รองรับฟังก์ชันระดับสูง ซึ่งเป็นฟังก์ชันที่รับฟังก์ชันอื่นๆ เป็นพารามิเตอร์ Compose พัฒนาต่อยอดจากแนวทางนี้ ตัวอย่างเช่น ฟังก์ชันคอมโพสิเบิล Button
จะมีพารามิเตอร์ LAMBDA onClick
ค่าของพารามิเตอร์นั้นคือฟังก์ชันที่ปุ่มเรียกใช้เมื่อผู้ใช้คลิก
Button( // ... ) // ...
ฟังก์ชันที่มีลำดับสูงกว่าจะจับคู่อย่างเป็นธรรมชาติกับนิพจน์แลมบ์ดา นิพจน์อื่นๆ
ซึ่งประเมินผลฟังก์ชันได้ หากต้องการใช้ฟังก์ชันเพียงครั้งเดียว คุณไม่จำเป็นต้องกำหนดฟังก์ชันนั้นที่อื่นเพื่อส่งไปยังฟังก์ชันระดับสูงขึ้น อย่างไรก็ตาม คุณสามารถ
ก็แค่กำหนดฟังก์ชันตรงนั้นด้วยนิพจน์แลมบ์ดา ตัวอย่างก่อนหน้านี้จะถือว่ามีการกําหนด myClickFunction()
ไว้ที่อื่น แต่หากใช้ฟังก์ชันนั้นที่นี่เท่านั้น ก็กำหนดฟังก์ชันในบรรทัดนั้นด้วยนิพจน์ Lambda ได้เลย ดังนี้
Button( // ... // do something // do something else } ) { /* ... */ }
แลมบ์ดาต่อท้าย
Kotlin มีไวยากรณ์พิเศษสำหรับการเรียกใช้ฟังก์ชันที่มีลำดับสูงกว่าซึ่งคำสั่งท้ายสุด คือ lambda หากต้องการส่งนิพจน์ Lambda เป็นพารามิเตอร์นั้น ให้ใช้ไวยากรณ์ Lambda ต่อท้าย แทนที่จะใส่นิพจน์ lambda ภายในวงเล็บ คุณก็ใส่ข้อความ หลังจากนั้น นี่เป็นสถานการณ์ที่พบได้ทั่วไปใน Compose คุณจึงต้องคุ้นเคยกับลักษณะของโค้ด
ตัวอย่างเช่น พารามิเตอร์สุดท้ายกับทุกเลย์เอาต์ เช่น พารามิเตอร์
Column()
ฟังก์ชัน Composable คือ content
ซึ่งเป็นฟังก์ชันที่แสดง UI ย่อย
จากองค์ประกอบเหล่านี้ สมมติว่าคุณต้องการสร้างคอลัมน์ที่มีองค์ประกอบข้อความ 3 รายการ
และจำเป็นต้องใช้การจัดรูปแบบบางอย่าง โค้ดนี้ใช้งานได้ แต่
ยุ่งยาก:
Column( modifier = Modifier.padding(16.dp), content = { Text("Some text") Text("Some more text") Text("Last text") } )
เนื่องจากพารามิเตอร์ content
เป็นพารามิเตอร์สุดท้ายในลายเซ็นฟังก์ชัน และเราส่งค่าของพารามิเตอร์เป็นนิพจน์ Lambda เราจึงดึงพารามิเตอร์นี้ออกจากวงเล็บได้ ดังนี้
Column(modifier = Modifier.padding(16.dp)) { Text("Some text") Text("Some more text") Text("Last text") }
ทั้ง 2 ตัวอย่างมีความหมายเหมือนกัน วงเล็บปีกกาหมายถึงเครื่องหมายแลมบ์ดา
นิพจน์ที่ส่งไปยังพารามิเตอร์ content
อันที่จริง หากพารามิเตอร์เดียวที่คุณส่งคือ lambda ที่ต่อท้าย นั่นคือ
หากพารามิเตอร์สุดท้ายคือ lambda และคุณไม่ได้ส่งผ่านพารามิเตอร์อื่น
คุณสามารถข้ามวงเล็บได้ ตัวอย่างเช่น สมมติว่าคุณ
ไม่จำเป็นต้องส่งแป้นกดร่วมไปยัง Column
คุณสามารถเขียนโค้ดอย่างเช่น
ดังนี้
Column { Text("Some text") Text("Some more text") Text("Last text") }
ไวยากรณ์นี้พบได้บ่อยในเครื่องมือเขียน โดยเฉพาะสำหรับองค์ประกอบเลย์เอาต์อย่าง Column
พารามิเตอร์สุดท้ายคือนิพจน์ Lambda ที่กําหนดองค์ประกอบย่อยขององค์ประกอบ และองค์ประกอบย่อยเหล่านั้นจะระบุไว้ในวงเล็บเหลี่ยมหลังการเรียกฟังก์ชัน
สโคปและตัวรับสัญญาณ
บางเมธอดและพร็อพเพอร์ตี้บางอย่างจะใช้ได้ในบางขอบเขตเท่านั้น ข้อจำกัด ขอบเขตช่วยให้คุณสามารถนำเสนอฟังก์ชันเมื่อจำเป็น และหลีกเลี่ยง ใช้ฟังก์ชันนั้นในทางที่ไม่เหมาะสม
ลองดูตัวอย่างที่ใช้ในเครื่องมือเขียน เมื่อคุณเรียกใช้Row
layout
composable ระบบจะเรียกใช้ Lambda ของเนื้อหาโดยอัตโนมัติภายใน RowScope
ซึ่งจะช่วยให้ Row
แสดงฟังก์ชันการทำงานที่ใช้งานได้ภายใน Row
เท่านั้น
ตัวอย่างด้านล่างแสดงวิธีที่ Row
แสดงค่าเฉพาะแถวสําหรับตัวแก้ไข align
Row { Text( text = "Hello world", // This Text is inside a RowScope so it has access to // Alignment.CenterVertically but not to // Alignment.CenterHorizontally, which would be available // in a ColumnScope. modifier = Modifier.align(Alignment.CenterVertically) ) }
API บางรายการยอมรับ Lambda ที่เรียกใช้ในขอบเขตผู้รับ แลมบ์ดาพวกนั้น มีสิทธิ์เข้าถึงพร็อพเพอร์ตี้และฟังก์ชันที่กำหนดไว้ที่อื่น ตาม การประกาศพารามิเตอร์:
Box( modifier = Modifier.drawBehind { // This method accepts a lambda of type DrawScope.() -> Unit // therefore in this lambda we can access properties and functions // available from DrawScope, such as the `drawRectangle` function. drawRect( /*...*/ /* ... ) } )
สำหรับข้อมูลเพิ่มเติม โปรดดูลิเทอรัลฟังก์ชันที่มี รีซีฟเวอร์ ในเอกสารประกอบของ Kotlin
ที่พักที่มอบสิทธิ์
Kotlin รองรับแบบตัวแทน
พร็อพเพอร์ตี้
ระบบจะเรียกพร็อพเพอร์ตี้เหล่านี้ราวกับว่าเป็นช่อง แต่ค่าคือ
ซึ่งกำหนดแบบไดนามิกโดยการประเมินนิพจน์ คุณระบุพร็อพเพอร์ตี้เหล่านี้ได้จากการใช้ไวยากรณ์ by
class DelegatingClass { var name: String by nameGetterFunction() // ... }
รหัสอื่นๆ จะเข้าถึงพร็อพเพอร์ตี้ได้ด้วยรหัสลักษณะนี้
val myDC = DelegatingClass() println("The name property is: " + myDC.name)
เมื่อ println()
ทำงาน ระบบจะเรียกใช้ nameGetterFunction()
เพื่อแสดงผลค่า
ของสตริง
พร็อพเพอร์ตี้ที่รับมอบสิทธิ์เหล่านี้มีประโยชน์อย่างยิ่งเมื่อคุณทำงานกับพร็อพเพอร์ตี้ที่ระบบจัดการข้อมูลของรัฐสนับสนุน
var showDialog by remember { mutableStateOf(false) } // Updating the var automatically triggers a state change showDialog = true
การจัดโครงสร้างข้อมูลคลาส
หากกําหนดคลาสข้อมูล คุณจะเข้าถึงข้อมูลได้ง่ายๆ ด้วยคําประกาศการแยกโครงสร้าง ตัวอย่างเช่น สมมติว่าคุณกำหนดคลาส Person
ดังนี้
data class Person(val name: String, val age: Int)
หากมีออบเจ็กต์ประเภทดังกล่าว คุณจะเข้าถึงค่าได้ด้วยโค้ดอย่างเช่นตัวอย่างนี้
val mary = Person(name = "Mary", age = 35) // ... val (name, age) = mary
คุณมักจะเห็นโค้ดประเภทนี้ในฟังก์ชัน เขียน:
Row { val (image, title, subtitle) = createRefs() // The `createRefs` function returns a data object; // the first three components are extracted into the // image, title, and subtitle variables. // ... }
คลาสข้อมูลมีฟังก์ชันที่มีประโยชน์อื่นๆ มากมาย ตัวอย่างเช่น เมื่อคุณ
กำหนดคลาสข้อมูลแล้วคอมไพเลอร์จะกำหนดฟังก์ชันที่เป็นประโยชน์โดยอัตโนมัติ เช่น
equals()
และ copy()
ดูข้อมูลเพิ่มเติมได้ในข้อมูล
ชั้นเรียน
วัตถุ Singleton
Kotlin ช่วยให้คุณประกาศคลาสแบบ Singleton ได้ง่าย ซึ่งเป็นคลาสที่มีอินสแตนซ์เพียงรายการเดียวเสมอ ระบบจะประกาศเดี่ยวเหล่านี้ด้วยคีย์เวิร์ด object
คอมโพสิชันมักใช้ออบเจ็กต์ดังกล่าว ตัวอย่างเช่น MaterialTheme
ได้รับการกําหนดให้เป็นออบเจ็กต์แบบ Singleton โดยพร็อพเพอร์ตี้ MaterialTheme.colors
, shapes
และ typography
ทั้งหมดมีค่าสำหรับธีมปัจจุบัน
ตัวสร้างและ DSL ที่ปลอดภัยตามประเภท
Kotlin อนุญาตให้สร้างภาษาเฉพาะโดเมน (DSL) ด้วยเครื่องมือสร้างที่ปลอดภัย DSL ช่วยให้สร้างข้อมูลลำดับชั้นที่ซับซ้อนได้ ในลักษณะที่สามารถดูแลรักษาและอ่านได้ง่ายขึ้น
Jetpack Compose ใช้ DSL สำหรับ API บางรายการ เช่น LazyRow
และ LazyColumn
@Composable fun MessageList(messages: List<Message>) { LazyColumn { // Add a single item as a header item { Text("Message List") } // Add list of messages items(messages) { message -> Message(message) } } }
Kotlin รับประกันเครื่องมือสร้างที่ปลอดภัยด้วยการพิมพ์โดยใช้
ทำงานตรงตัวกับตัวรับ
หากเราเลือก Canvas
Composable เป็นตัวอย่าง ซึ่งจะใช้เป็นพารามิเตอร์เป็นฟังก์ชันที่มี
DrawScope
เป็นตัวรับ onDraw: DrawScope.() -> Unit
เพื่อให้การบล็อกโค้ด
เรียกใช้ฟังก์ชันสมาชิกที่กำหนดไว้ใน DrawScope
Canvas(Modifier.size(120.dp)) { // Draw grey background, drawRect function is provided by the receiver drawRect(color = Color.Gray) // Inset content by 10 pixels on the left/right sides // and 12 by the top/bottom inset(10.0f, 12.0f) { val quadrantSize = size / 2.0f // Draw a rectangle within the inset bounds drawRect( size = quadrantSize, color = Color.Red ) rotate(45.0f) { drawRect(size = quadrantSize, color = Color.Blue) } } }
ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวสร้างที่ปลอดภัยต่อประเภทและ DSL ในเอกสารประกอบของ Kotlin
โครูทีน Kotlin
Coroutines เสนอการสนับสนุนการเขียนโปรแกรมแบบไม่พร้อมกันในระดับภาษาใน Kotlin Coroutine จะระงับการดำเนินการโดยไม่บล็อกเทรดได้ UI แบบตอบสนองจะทำงานแบบไม่พร้อมกันโดยพื้นฐาน และ Jetpack Compose แก้ปัญหานี้ด้วยการรองรับ coroutine ที่ระดับ API แทนการใช้การเรียกกลับ
Jetpack Compose มี API ที่ทำให้การใช้ Coroutine ปลอดภัยภายในเลเยอร์ UI
rememberCoroutineScope
จะแสดงผล CoroutineScope
ที่คุณสามารถสร้างโครูทีนในเครื่องจัดการเหตุการณ์และการเรียกใช้
เขียน API ที่ระงับ ดูตัวอย่างด้านล่างโดยใช้ animateScrollTo
API ของ ScrollState
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... // Create a new coroutine that scrolls to the top of the list // and call the ViewModel to load data composableScope.launch { scrollState.animateScrollTo(0) // This is a suspend function viewModel.loadData() } } ) { /* ... */ }
Coroutine จะเรียกใช้บล็อกโค้ดตามลําดับโดยค่าเริ่มต้น การวิ่ง
Coroutine ที่เรียกใช้ฟังก์ชันระงับจะระงับการดำเนินการจนกว่าฟังก์ชัน
ฟังก์ชันระงับการส่งคืน ถึงแม้ฟังก์ชันระงับจะย้ายฟังก์ชัน
ไปยัง CoroutineDispatcher
อื่น ในตัวอย่างก่อนหน้านี้
loadData
จะไม่ถูกดำเนินการจนกว่าจะใช้ฟังก์ชันการระงับ animateScrollTo
ที่เกินออกมา
หากต้องการเรียกใช้โค้ดพร้อมกัน คุณต้องสร้างโคโรทีนใหม่ ในตัวอย่างข้างต้น หากต้องการเลื่อนขึ้นด้านบนของหน้าจอและโหลดข้อมูลจาก viewModel
พร้อมกัน คุณต้องใช้ coroutine 2 รายการ
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... // Scroll to the top and load data in parallel by creating a new // coroutine per independent work to do composableScope.launch { scrollState.animateScrollTo(0) } composableScope.launch { viewModel.loadData() } } ) { /* ... */ }
Coroutines ช่วยให้คุณรวม API แบบไม่พร้อมกันได้ง่ายขึ้น ดังต่อไปนี้
ตัวอย่างเช่น เราจะรวมตัวแก้ไข pointerInput
เข้ากับ API ของภาพเคลื่อนไหวเพื่อ
ทำให้ตำแหน่งขององค์ประกอบเคลื่อนไหวเมื่อผู้ใช้แตะหน้าจอ
@Composable fun MoveBoxWhereTapped() { // Creates an `Animatable` to animate Offset and `remember` it. val animatedOffset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } Box( // The pointerInput modifier takes a suspend block of code Modifier .fillMaxSize() .pointerInput(Unit) { // Create a new CoroutineScope to be able to create new // coroutines inside a suspend function coroutineScope { while (true) { // Wait for the user to tap on the screen val offset = awaitPointerEventScope { awaitFirstDown().position } // Launch a new coroutine to asynchronously animate to // where the user tapped on the screen launch { // Animate to the pressed position animatedOffset.animateTo(offset) } } } } ) { Text("Tap anywhere", Modifier.align(Alignment.Center)) Box( Modifier .offset { // Use the animated offset as the offset of this Box IntOffset( animatedOffset.value.x.roundToInt(), animatedOffset.value.y.roundToInt() ) } .size(40.dp) .background(Color(0xff3c1361), CircleShape) ) }
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Coroutines ได้ที่ คำแนะนำเกี่ยวกับโครูทีน Kotlin ใน Android
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- คอมโพเนนต์และเลย์เอาต์ของ Material Design
- ผลข้างเคียงในโหมดเขียน
- ข้อมูลเบื้องต้นเกี่ยวกับการจัดวาง