[Compose] Jetpack Compose(1)
Adrian Chen

關於我為什麼突然開始學Compose,我有幾點想說。
首先,Jetpack Compose是2019年釋出的新一代Android UI Framework,取代了之前傳統的XML,並且已經演化穩定,現在更是Android開發的預設UI Framework。這個Framework還正年輕,前途不可限量。
其次,JB公司新釋出的Kotlin Multiplatform也終於開始支援使用Compose進行多平台的UI開發了,這樣子結合起之前釋出的KMM,Kotlin做多平台似乎變得更加優雅了。由於Android設計師可以可以毫無壓力轉到Kotlin Multiplatform,我個人認為,它日後將成為取代Flutter的一把利刃。
還有就是它的這種UI宣告方式,是使用lambda表達式做引數來進行巢狀UI元件的構建,雖然這相對於Flutter來講是一個小小的改動(當然只是看起來,一個函式一個型別建構子,想想都知道是天差地遠),但是在我的觀念裡,這真的有變得更加合理了。真的比Flutter那種什麼都塞到建構子裏的方法來得更優雅,也更符合直覺。

Composable Functions

在Compose中,使用@composable annotation標註的函式即為Composable functions。這類函式被用來描述UI的階層。比如:

1
2
3
4
5
6
7
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}

在MainActivity的setContent()函式中,不需要再像以前那樣使用xml來定義UI,而是直接呼叫已經定義的composable函式即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}

我們可以看到Compose提供的UI元件有一部分是容器類的,即最後一個引數是函式。於是我們可以使用lambda表達式來表示巢狀結構。

@Preview annotation

使用@Preview這個annotation可以實現預覽composable的元件。必須有這樣一個annotation,否則無論是預覽窗還是AVD,都不會有顯示,甚至會有報錯。

Modifier

大部分的Compose UI元件,都會提供一個引數名為modifier,可以傳入一個Modifier類型的函式。這個引數用於修飾元素的顯示狀態,即Web開發中的CSS。你可以吧該引數看作是HTML元素的style

1
2
3
4
5
6
7
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier.padding(24.dp)
)
}

Layout

Compose提供三個元件,分別是ColumnRowBox。這三個元件是基本的版面配置元件。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.ElevatedButton
// ...

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello ")
Text(text = name)
}
ElevatedButton(
onClick = { /* TODO */ }
) {
Text("Show more")
}
}
}
}

Compose狀態管理

對於一個Web開發人員,尤其是對於一個有學習過Vue.js和React的Web開發人員來講,狀態管理這個概念實在是不應該陌生。

在Vue.js中,使用有狀態的變數,應該使用ref()reactive()函式包圍,然後使用<name>.value來呼叫它。

在Compose中,有異曲同工之妙。在Compose中,我們這樣定義有狀態的變數:

1
val expanded = remember { mutableStateOf(false) }

然後一樣使用expanded.value去呼叫它就好了。

定義式可以做如下解釋:

  1. mutableStateOf()函式:標記該變數是一個有狀態的變數。
  2. remember {}:記住該變數的狀態,防止更新元件的時候該變數被重置。

狀態提升

在Compose中的狀態提升幾乎和在React中一模一樣。都是把狀態變數放到共同的父元素中,然後透過函式的向下呼叫來修改父元素中的狀態。

給一個例子,這應該非常熟悉了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Composable
fun MyApp(modifier: Modifier = Modifier) {

var shouldShowOnboarding by remember { mutableStateOf(true) }

Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}

@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {

Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier
.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}

}

使用Kotlin的by委派功能,我們可以避免每次都使用值,這是一種推薦的做法。

狀態保留

如果我們希望在執行、旋轉畫面、變更為深色模式,或是終止程序之後,都能夠保留狀態,我們僅需要把remember {}變成rememberSavable{}即可。