適用於 Jetpack Compose 的 Kotlin

Jetpack Compose 是以 Kotlin 建構而成,在某些情況下,Kotlin 提供與 可協助您輕鬆編寫優質 Compose 程式碼的慣用語。如果您從另一個 並將該語言解讀為 Kotlin 可能會錯過 Compose 的優勢,而您可能會發現 難以理解以慣用語編寫的 Kotlin 程式碼增加 熟悉 Kotlin 的樣式有助於避免這些問題。

預設引數

編寫 Kotlin 函式時,您可以指定「函式引數的預設值」,在呼叫者未明確傳送這些值時使用。這項功能可減少對超載函式的需求。

舉例來說,假設您想要編寫可繪製正方形的函式。沒錯 函式可能有一個必要參數 sideLength,用於指定長度 每個階段的一方該參數可能包含數個選用參數,例如 thicknessedgeColor 等;如果呼叫端未指定這些參數,則函式會使用預設值。如果是其他語言,可能會以 幾項函式

// 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")
}

這兩個範例的意義完全相同。括號定義了傳送至 content 參數的 lambda 運算式。

事實上,如果您傳遞的「唯一」參數是結尾的 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 說明文件中的「具有接收端的函式常值」。

委派屬性

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,例如: LazyRowLazyColumn

@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。請參閱以下範例 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 修飾符號與 Animation 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 協同程式指南。

目前沒有任何建議。

建議 Google 帳戶。