適用於 Jetpack Compose 的 Kotlin

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()敬上 可組合函式為 content,會輸出子項 UI 元素。假設您想建立一個包含三個文字元素的資料欄 然後您必須套用一些格式這段程式碼有效 黃體:

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.colorsshapestypography 屬性全都包含目前主題的值。

型別安全建構工具和 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。協同程式可以在不封鎖執行緒的情況下暫停執行。A 罩杯 回應式 UI 原本就具備非同步性質,而 Jetpack Compose 會自動解決這個問題 不要使用回呼,而是在 API 層級採用協同程式。

您可以運用 Jetpack Compose 提供的 API,在 UI 層中安全地使用協同程式。 rememberCoroutineScope 函式會傳回 CoroutineScope,您可以在事件處理常式中建立協同程式,並呼叫 Compose 暫停 API。請參閱以下範例 ScrollStateanimateScrollTo 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 協同程式」指南。