Jetpack Compose 是以 Kotlin 建構而成,在某些情況下,Kotlin 提供與 可協助您輕鬆編寫優質 Compose 程式碼的慣用語。如果您從另一個 並將該語言解讀為 Kotlin 可能會錯過 Compose 的優勢,而您可能會發現 難以理解以慣用語編寫的 Kotlin 程式碼增加 熟悉 Kotlin 的樣式有助於避免這些問題。
預設引數
編寫 Kotlin 函式時,您可以指定函式的預設值 引數, 在呼叫端未明確傳遞這些值時使用。這項功能可降低 您就不需要為超載函式而設計
舉例來說,假設您想要編寫可繪製正方形的函式。沒錯 函式可能有一個必要參數 sideLength,用於指定長度 每個階段的一方可能有多個選用參數,例如粗細。 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
敬上
可組合函式提供 onClick
lambda 參數。該參數的值為函式,使用者點選按鈕後就會呼叫此函式:
Button( // ... onClick = myClickFunction ) // ...
高階函式會與 lambda 運算式、運算式自然地配對
每個函式的價目值如果只需要該函式一次,則不會
然後將其傳遞至高階函式您可以改用直接利用 lambda 運算式定義函式。上一個例子
我們會假設 myClickFunction()
已在其他位置定義。但請注意
函式,較簡單的做法是只用 lambda 內嵌內嵌函式
運算式:
Button( // ... onClick = { // do something // do something else } ) { /* ... */ }
結尾的 lambda
Kotlin 提供一種特殊語法,可以呼叫具有 last 的高階函式 參數就是 lambda。如要將 lambda 運算式做為 參數,您可以使用 結尾 lambda 語法。 您不應將 lambda 運算式放在括號內 說明。這是 Compose 的常見情況,因此您必須熟悉 以及程式碼的外觀
舉例來說,所有版面配置的最後一個參數 (例如 Column()
可組合函式) 是會發送子項 UI 元素函式的 content
。假設您想建立一個包含三個文字元素的資料欄
然後您必須套用一些格式這段程式碼有效
黃體:
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") }
這兩個範例的含義完全相同。大括號會定義 lambda
傳遞至 content
參數的運算式。
事實上,如果您傳遞的「唯一」參數是結尾的 lambda,也就是
如果最終參數是 lambda,且您並未傳送任何其他參數
參數,可以完全省略括號。例如,假設您不需要將修飾符號傳送至 Column
。您可以將程式碼編寫為
:
Column { Text("Some text") Text("Some more text") Text("Last text") }
這個語法在 Compose 中很常見,尤其是對於像
Column
。最後一個參數是定義元素
這些子項都會在函式呼叫之後,用大括號指定。
範圍和接收器
部分方法和屬性僅適用於特定範圍。有限 可讓您視需要提供功能 以套用功能
請考慮使用 Compose 撰寫程式碼時使用範例。呼叫 Row
版面配置時
可組合函式會在 RowScope
中自動叫用內容 lambda。
這可讓 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。這些 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 支援「委派」
資源
這些屬性看似和欄位相似,但其值由計算運算式來動態決定。對於這些你來說
方法是使用 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
您經常會在 Compose 函式中看到這類程式碼:
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()
》。您可以參閱資料
類別說明文件。
單例模式物件
Kotlin 可讓您輕鬆宣告「單例模式」,這些類別一律只有一個執行個體。系統會使用 object
關鍵字宣告這些單例模式。
Compose 通常會使用這類物件。例如:
MaterialTheme
是
定義為單例模式物件MaterialTheme.colors
、shapes
和
typography
屬性全都包含目前主題的值。
型別安全建構工具和 DSL
Kotlin 可讓您建立特定領域語言 (DSL) 使用符合類型安全的建構工具DSL 可用來建構複雜的階層式資料 更容易維護且容易理解
Jetpack Compose 在某些 API 中使用 DSL,例如:
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
做為參數
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 協同程式
協同程式可在 Kotlin。協同程式可以在不封鎖執行緒的情況下暫停執行。回應式 UI 本身的性質為非同步,Jetpack Compose 解決這個問題的方法是在 API 級別採用協同程式,而不使用回呼。
您可以運用 Jetpack Compose 提供的 API,在 UI 層中安全地使用協同程式。rememberCoroutineScope
函式會傳回 CoroutineScope
,您可以借助該函式建立協同程式,並呼叫 Compose 暫停 API。請參閱以下範例
ScrollState
的
animateScrollTo
API。
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... onClick = { // 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() } } ) { /* ... */ }
根據預設,協同程式會「依序」執行程式碼區塊。跑步
呼叫暫停函式的協同程式會「暫停」執行作業,直到
暫停函式傳回值。即使暫停函式將
執行作業至不同的 CoroutineDispatcher
。在先前的範例中
只有在暫停函式 animateScrollTo
後才會執行 loadData
就會傳回值。
如要同時執行程式碼,必須建立新的協同程式。這個例子
這樣就能平行捲動至螢幕頂端,並從中載入資料
viewModel
,需要兩個協同程式。
// Create a CoroutineScope that follows this composable's lifecycle val composableScope = rememberCoroutineScope() Button( // ... onClick = { // 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() } } ) { /* ... */ }
協同程式可讓您輕鬆結合非同步 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) ) }
如要進一步瞭解協同程式,請參閱 「Android 上的 Kotlin 協同程式」指南。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- Material Design 元件和版面配置
- Compose 中的連帶效果
- Compose 版面配置的基本概念