Google 致力于为黑人社区推动种族平等。查看具体举措

Kotlin 对 Jetpack Compose 的支持

Jetpack Compose 围绕 Kotlin 构建而成。在某些情况下,Kotlin 提供了一些特殊的惯用语,这使编写良好的 Compose 代码变得更容易。如果您用另一种编程语言思考,并在头脑中将这种语言翻译成 Kotlin,很可能会错失 Compose 的一些优势,还可能会发现很难理解以惯用语编写的 Kotlin 代码。进一步熟悉 Kotlin 的样式可帮助您避免这些隐患。

默认参数

编写 Kotlin 函数时,您可以指定函数参数的默认值;如果调用方未明确传递相应的值,就会使用这些默认值。此功能减少了对重载函数的需求。

例如,假设您要编写一个绘制正方形的函数。该函数可能有一个必需参数 side,用于指定每条边的长度。它可能有几个可选参数,如 thicknessedgeColorfill 等等;如果调用方未指定这些参数的值,该函数就会使用默认值。在其他语言中,您可能需要编写多个函数:

// We don't need to do this in Kotlin!
void drawSquare(int side) {...}
void drawSquare(int side, int thickness) {...}
void drawSquare(int side, int thickness, Color edgeColor ) {...}
// …

在 Kotlin 中,您可以编写一个函数并指定参数的默认值:

fun drawSquare(
    side: Int, thickness: Int = 2,
    edgeColor: Color = Color.Black,
    fill: Color = Color.White
) { ... }

除了让您不必编写多个冗余函数之外,此功能还会使代码读起来更清晰。如果调用方未指定参数的值,这表明它们愿意使用默认值。此外,命名参数可让您更容易看到发生了什么。如果您查看代码并看到这样的函数调用,在不检查 drawSquare() 代码的情况下,您可能不知道参数的含义:

drawSquare(30, 5, Color.Red);

相比之下,以下代码具有自描述性:

drawSquare(side = 30, thickness = 5, edgeColor = Color.Red)

大多数 Compose 库都使用默认参数,让您编写的可组合函数同样也使用默认参数是一种很好的做法。这种做法使可组合项可自定义,但仍使默认行为易于调用。例如,您可以创建一个简单的文本元素,如下所示:

Text(text = "Hello, Android!")

该代码与以下代码的效果相同,但以下代码更为冗长,其中明确设置了更多 Text 参数:

Text(text = "Hello, Android!",
    color = Color.Unset,
    fontSize = TextUnit.Inherit,
    letterSpacing = TextUnit.Inherit,
    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 提供了一种特殊语法来调用最后一个参数为 lambda 的高阶函数。如果您要将一个 lambda 表达式作为该参数传递,您可以使用尾随 lambda 语法您应将 lambda 表达式放在圆括号后面,而不是将其放在圆括号内。这是 Compose 中的一种常见情况,因此您需要熟悉代码是什么样子的。

例如,所有布局的最后一个参数(如 Column() 可组合函数)均为 content,它是一个发出列中的界面元素的函数。假设您想要创建一个包含三个文本元素的列,并且需要应用某种格式设置。以下代码行得通,但它非常繁琐:

Column(
    modifier = Modifier.padding(16.dp), content = {
        Text("Some text")
        Text("Some more text")
        Text("Last text")
    }
)

由于 children 参数是函数签名中的最后一个参数,并且我们要将其值作为 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 之类的布局元素。最后一个参数是一个 lambda 表达式,它用于定义元素的子元素,这些子元素在函数调用后在大括号中指定。

范围和接收器

有些方法和属性仅在某一范围内可用。限定的范围可让您在需要之处提供相关功能,并避免意外地在不当之处使用该功能。

下面我们考虑一下 Compose 中使用的一个示例。当您调用 Row 布局可组合项时,系统会自动在 RowScope 中调用内容 lambda。这样一来,Row 就可以提供仅在 Row 中有效的功能。以下示例演示了 Row 如何为 gravity 修饰符提供行专用值:

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.gravity(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.
        drawRectangle(...)
    }
)

如需了解详情,请参阅 Kotlin 文档中的具有接收器的函数字面量

委托属性

Kotlin 支持委托属性。这些属性就像字段一样被调用,但它们的值是通过对表达式求值动态确定的。这些属性使用 by 语法,您可以据此识别它们:

class delegatingClass {
    var name: String by nameGetterFunction()
}

其他代码可以访问此类属性,所用代码如下:

myDC = delegatingClass()
println("The name property is: " + myDC.name)

println() 执行时,系统会调用 nameGetterFunction() 以返回字符串的值。

当您使用状态支持的属性时,这些委托属性特别有用:

var showDialog by state { 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 函数中看到这样的代码:

ConstraintLayout {

    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 属性都包含当前主题背景的值。