Android前沿技術—— Jetpack Compose
theme: channing-cyan
簡介
Jetpack Compose 是一款新型工具包,旨在幫助簡化介面開發。該工具包將響應式程式設計模型與簡潔易用的 Kotlin 程式語言相結合,並採用完全宣告式的程式碼編寫方式,讓您可以通過呼叫一系列函式來描述介面,這些函式會將資料轉換為介面層次結構。當底層資料發生變化時,框架會自動重新執行這些函式,為您更新介面層次結構。
Compose 應用由可組合函式構成。可組合函式即帶有 @Composable
標記的常規函式,這些函式可以呼叫其他可組合函式。使用一個函式就可以建立一個新的介面元件。該註解會告知 Compose 為函式新增特殊支援,以便後續更新和維護介面。藉助 Compose,您可以將程式碼設計成多個小程式碼塊。可組合函式通常簡稱為“可組合項”。
通過建立可重用的小型可組合項,您可以輕鬆構建應用中所用介面元素的庫。每個可組合項對應於螢幕的一個部分,可以單獨修改。
Compose 使用入門
瞭解 Android Studio 為您生成的與 Compose 相關的各種類和方法。
可組合函式
可組合函式是帶有 @Composable
註解的常規函式。這類函式自身可以呼叫其他 @Composable
函式。我們會展示如何為 Greeting
函式新增 @Composable
標記。此函式會生成一段顯示給定輸入 String
的介面層次結構。Text
是由庫提供的可組合函式。
@Composableprivate fun Greeting(name: String) { Text(text = "Hello $name!")}
注意:可組合函式是帶有 @Composable
註解的 Kotlin 函式,如上述程式碼段所示。
Android 應用中的 Compose
使用 Compose 時,Activity
仍然是 Android 應用的入口點。在我們的專案中,使用者開啟應用時會啟動 MainActivity
(如 AndroidManifest.xml
檔案中所指定)。您可以使用 setContent
來定義佈局,但不同於在傳統 View 系統中使用 XML 檔案,您將在該函式中呼叫可組合函式。
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.colors.background
) {
Greeting("Android")
}
}
}
}
}
BasicsCodelabTheme
是為可組合函式設定樣式的一種方式。有關詳細內容,請參閱設定應用主題部分。如需檢視文字在螢幕上的顯示效果,您可以在模擬器或裝置上執行該應用,或使用 Android Studio 預覽進行檢視。
若要使用 Android Studio 預覽,您只需使用 @Preview
註解標記所有無引數可組合函式或採用預設引數的函式,然後構建您的專案即可。現在 MainActivity.kt
檔案中已經包含了一個 Preview Composable
函式。您可以在同一個檔案中包含多個預覽,併為它們指定名稱。
@Preview(showBackground = true, name = "Text preview")
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greeting(name = "Android")
}
}
注意:在此專案中匯入與 Jetpack Compose 相關的類時,請從以下位置匯入:
- androidx.compose.*(針對編譯器和執行時類)
- androidx.compose.ui.*(針對介面工具包和庫)
如果選擇 Code ,系統可能不會顯示預覽。請點選 Split
以檢視預覽。
調整介面
首先,為 Greeting
設定不同的背景顏色。為此,您可以用 Surface
包圍 Text
可組合項。Surface
會採用一種顏色,因此請使用 MaterialTheme.colors.primary
。
注意:Surface
和 MaterialTheme
是與 Material Design 相關的概念。Material Design 是 Google 提供的一個設計系統,旨在幫助您構建介面和體驗。
@Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Text (text = "Hello $name!")
}
}
巢狀在 Surface
內的元件將在該背景顏色之上繪製。
將上述程式碼新增到專案後,您會在 Android Studio 的右上角看到 Build & Refresh 按鈕。點按該按鈕或構建專案即可在預覽中檢視新更改。
您可以在預覽中檢視新更改:
您可能忽略了一個重要的細節:文字現在是白色的。我們是何時對此進行定義的?
我們並沒有對此進行過定義!Material 元件(例如 androidx.compose.material.Surface
)旨在提供應用中可能需要的常見功能(例如為文字選擇適當的顏色),讓您獲得更好的體驗。我們之所以說 Material 很實用,是因為它提供在大多數應用中都會用到的實用預設值和模式。Compose 中的 Material 元件是在其他基礎元件(位於 androidx.compose.foundation
中)的基礎上構建的。如果您需要更高的靈活性,也可以從您的應用元件中訪問這些元件。
在這種情況下,Surface
會了解,當該背景設定為 primary
顏色後,其上的任何文字都應使用 onPrimary
顏色,此顏色也在主題中進行了定義。如需瞭解詳情,請參閱設定應用主題部分。
注意:如需檢視 Compose 中 Material 元件的互動式列表,請檢視 Compose Material Catalog 應用。
修飾符
大多數 Compose 介面元素(例如 Surface
和 Text
)都接受可選的 modifier
引數。修飾符會指示介面元素如何在其父級佈局中放置、顯示或表現。
例如,padding
修飾符會在其修飾的元素周圍應用一定的空間。您可以使用 Modifier.padding()
建立內邊距修飾符。
現在,為螢幕上的 Text
新增內邊距:
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
...
@Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
}
}
點選 Build & Refresh 即可檢視新更改。
有數十種修飾符可用於實現對齊、新增動畫、設定佈局、使可點選或可滾動以及轉換等效果。有關完整列表,請檢視 Compose 修飾符列表。您將在後續步驟中使用其中的部分修飾符。
重複使用可組合項
您新增到介面的元件越多,建立的巢狀層級就越多。如果函式變得非常大,可能會影響可讀性。通過建立可重用的小型元件,可以輕鬆構建應用中所用介面元素的庫。每個元件對應於螢幕的一個部分,可以單獨修改。
建立一個名為 MyApp
的可組合項,該組合項中包含問候語。
@Composable
private fun MyApp() {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
這樣一來,由於現在可以重複使用 MyApp
可組合項,您就可以省去 onCreate
回撥和預覽,從而避免重複編寫程式碼。您的 MainActivity.kt
檔案應如下所示:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basicstep1.ui.theme.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp()
}
}
}
}
@Composable
private fun MyApp() {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
@Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
}
}
@Preview(showBackground = true)
@Composable
private fun DefaultPreview() {
BasicsCodelabTheme {
MyApp()
}
}
建立列和行
Compose 中的三個基本標準佈局元素是 Column
、Row
和 Box
可組合項。
它們是接受可組合內容的可組合函式,因此您可以在其中放置專案。例如,Column
中的每個子級都將垂直放置。
// Don't copy over
Column {
Text("First row")
Text("Second row")
}
現在嘗試更改 Greeting
,使其顯示包含兩個文字元素的列,如以下示例中所示:
請注意,您可能需要移動周圍的內邊距。
將您的結果與此解決方案進行比較:
import androidx.compose.foundation.layout.Column
...
@Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Hello,")
Text(text = name)
}
}
}
Compose 和 Kotlin
可組合函式可以像 Kotlin 中的其他函式一樣使用。這會使介面構建變得非常有效,因為您可以新增語句來影響介面的顯示方式。
例如,您可以使用 for
迴圈向 Column
中新增元素:
@Composable
fun MyApp(names: List<String> = listOf("World", "Compose")) {
Column {
for (name in names) {
Greeting(name = name)
}
}
}
您尚未設定可組合項的尺寸,也未對可組合項的大小新增任何限制,因此每一行僅佔用可能的最小空間,預覽時的效果也是如此。讓我們更改預覽效果,以模擬小螢幕手機的常見寬度 320dp。按如下所示向 @Preview
註解新增 widthDp
引數:
@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
MyApp()
}
}
修飾符在 Compose 中使用得非常廣泛,現在我們來練習更高階的用法:嘗試使用 fillMaxWidth
和 padding
修飾符複製以下佈局。
現在,將您的程式碼與解決方案進行比較:
@Composable
fun MyApp(names: List<String> = listOf("World", "Compose")) {
Column(modifier = Modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Composable
private fun Greeting(name: String) {
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
Text(text = "Hello, ")
Text(text = name)
}
}
}@Composablefun MyApp(names: List<String> = listOf("World", "Compose")) { Column(modifier = Modifier.padding(vertical = 4.dp)) { for (name in names) { Greeting(name = name) } }}@Composableprivate fun Greeting(name: String) { Surface( color = MaterialTheme.colors.primary, modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp) ) { Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) { Text(text = "Hello, ") Text(text = name) } }}
請注意:
- 修飾符可以包含過載,因而具有相應的優勢,例如您可以指定不同的方式來建立內邊距。
- 若要向一個元素新增多個修飾符,您只需要將它們連結起來即可。
有多種方式可以實現此結果,因此,如果您的程式碼與此程式碼段不同,並不表示您的程式碼就是錯的。不過,為了繼續完成此 Codelab,仍請複製並貼上此程式碼。
新增按鈕
接下來,您將新增一個用於展開 Greeting
的可點選元素,因此需要先新增對應的按鈕。您的目標是要建立以下佈局:
Button
是 Material 軟體包提供的一種可組合項,它採用可組合項作為最後一個引數。由於尾隨 lambda 可以移到括號之外,因此您可以向按鈕新增任何內容作為子級,例如 Text
:
// Don't copy yet
Button(
onClick = { } // You'll learn about this callback later
) {
Text("Show less")
}
注意:Compose 根據 Material Design 按鈕規範提供了不同型別的 Button
:Button
、OutlinedButton
和 TextButton
。在本示例中,您將使用包圍 Text
作為 Button
內容的 OutlinedButton
。
為了實現這一點,您需要學習如何在行尾放置可組合項。由於沒有 alignEnd
修飾符,因此您需要在開始時為該可組合項賦予一定的 weight
。weight
修飾符會讓元素填滿所有可用空間,使其“具有彈性”,也就是會推開其他沒有權重的元素(即“無彈性”元素)。該修飾符還會使 fillMaxWidth
修飾符變得多餘。
現在嘗試新增該按鈕,並按照上述圖片中所示放置該按鈕。
下面列出了對應的解決方案程式碼:
import androidx.compose.material.Button
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
...
@Composable
private fun Greeting(name: String) {
Surface(
color = MaterialTheme.colors.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)
}
OutlinedButton(
onClick = { /* TODO */ }
) {
Text("Show more")
}
}
}
}
Compose 中的狀態
在本部分中,您將向螢幕中新增一些互動。到目前為止,您已經建立了一些靜態佈局,現在您要讓它們響應使用者更改,以達到下面的效果:
在開始瞭解如何使按鈕可點選以及如何調整內容大小之前,您需要在某個位置儲存某個值,用於指示每項內容是否展開(即內容的狀態)。由於我們需要為每條問候語設定這兩個值之一,因此其邏輯位置位於 Greeting
可組合項中。我們來看看此 expanded
布林值及其在程式碼中的使用方式:
// Don't copy over
@Composable
private fun Greeting(name: String) {
var expanded = false // Don't do this!
Surface(
color = MaterialTheme.colors.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)
}
OutlinedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
請注意,我們還添加了 onClick
操作和動態按鈕文字。稍後會進一步介紹。
但是,此設定無法按預期發揮作用。為 expanded
變數設定不同的值不會使 Compose 將其檢測為狀態更改,因此不會產生任何效果。
Compose 應用通過呼叫可組合函式將資料轉換為介面。如果您的資料發生變化,Compose 會使用新資料重新執行這些函式,從而建立更新後的介面,此過程稱為重組。Compose 還會檢視各個可組合項需要哪些資料,以便只需重組資料發生了變化的元件,而避免重組未受影響的元件。
正如 Compose 程式設計思想一文中所述:
可組合函式可以按任意順序頻繁執行,因此您不能以程式碼的執行順序或該函式的重組次數為判斷依據。
更改此變數不會觸發重組的原因是 Compose 並未跟蹤此更改。此外,每次呼叫 Greeting
時,都會將該變數重置為 false。
如需向可組合項新增內部狀態,您可以使用 mutableStateOf
函式,該函式可讓 Compose 重組讀取該 State
的函式。
State
和 MutableState
是兩個介面,它們具有特定的值,每當該值發生變化時,它們就會觸發介面更新(重組)。
import androidx.compose.runtime.mutableStateOf
...
// Don't copy over
@Composable
fun Greeting() {
val expanded = mutableStateOf(false) // Don't do this!
}
但是,不能只是將 mutableStateOf
分配給可組合項中的某個變數。如前所述,重組可能會隨時發生,這會再次呼叫可組合項,從而將狀態重置為值為 false
的新可變狀態。
如需在重組後保留狀態,請使用 remember
記住可變狀態。
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
...
@Composable
fun Greeting() {
val expanded = remember { mutableStateOf(false) }
...
}
remember
可以起到保護作用,防止狀態在重組時被重置。
請注意,如果從螢幕的不同部分呼叫同一可組合項,則會建立不同的介面元素,且每個元素都會擁有自己的狀態版本。您可以將內部狀態視為類中的私有變數。
可組合函式會自動“訂閱”狀態。如果狀態發生變化,讀取這些欄位的可組合項將會重組以顯示更新。
更改狀態和響應狀態更改
您可能已經注意到,為了更改狀態,Button
具有一個名為 onClick
的引數,但它不接受值,而接受函式。
您可能不熟悉以這種方式使用的函式,這其實就是一種在 Compose 中廣泛使用的非常強大的 Kotlin 功能。函式是 Kotlin 中的首要元素,您可以將它們分配給某個變數,傳遞給其他函式,甚至可以從它們自身返回函式。您可以在此處瞭解 Compose 如何使用 Kotlin 功能。
如需詳細瞭解如何定義和例項化函式,請參閱函式型別文件。
您可以通過為“onClick”指定 lambda 表示式,定義點選時將執行的操作。例如,切換展開狀態的值,並根據該值顯示不同的文字。
OutlinedButton(
onClick = { expanded.value = !expanded.value },
) {
Text(if (expanded.value) "Show less" else "Show more")
}
如果在模擬器中執行應用,您會看到點選該按鈕時,expanded
會切換,從而觸發重組該按鈕內的文字。每個 Greeting
都具有自己的展開狀態,因為它們屬於不同的介面元素。
到目前為止的程式碼:
@Composable
private fun Greeting(name: String) {
val expanded = remember { mutableStateOf(false) }
Surface(
color = MaterialTheme.colors.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)
}
OutlinedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
展開內容
現在,我們來根據請求實際展開內容。新增一個依賴於狀態的額外變數:
@Composable
private fun Greeting(name: String) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
...
您無需在重組後記住 extraPadding
,因為它僅執行簡單的計算。
現在我們可以將新的內邊距修飾符應用於 Column:
@Composable
private fun Greeting(name: String) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
如果在模擬器上執行,您應該會看到每項內容均可單獨展開:
狀態提升
在可組合函式中,被多個函式讀取或修改的狀態應位於共同祖先實體中,此過程稱為狀態提升。“提升”是指“提高”或“升級”。
使狀態可提升,可以避免複製狀態和引入 bug,有助於重複使用可組合項,並大大降低可組合項的測試難度。相反,不需要由可組合項的父級控制的狀態則不應該被提升。可信來源屬於該狀態的建立者和控制者。
例如,讓我們來為應用建立一個初始配置螢幕。
將以下程式碼新增到 MainActivity.kt
:
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.foundation.layout.Arrangement
...
@Composable
fun OnboardingScreen() {
// TODO: This state should be hoisted
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = { shouldShowOnboarding = false }
) {
Text("Continue")
}
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen()
}
}
此程式碼包含多個新功能:
- 您已經添加了一個名為
OnboardingScreen
的新可組合項以及一個新的預覽。構建專案時,您會發現您可以同時擁有多個預覽。我們還添加了一個固定高度,以驗證內容是否正確對齊。 - 可以配置
Column
,使其在螢幕中心顯示其內容。 shouldShowOnboarding
使用的是by
關鍵字,而不是=
。這是一個屬性委託,可讓您無需每次都輸入.value
。- 點選該按鈕時,會將
shouldShowOnboarding
設為false
,儘管您並未從任何位置讀取該狀態。
現在,我們即可將這個新的初始配置螢幕新增到應用。我們希望該螢幕在應用啟動時顯示,然後在使用者按“繼續”時隱藏。
在 Compose 中,您不會隱藏介面元素,因為不會將它們新增到組合中,因此它們也不會新增到 Compose 生成的介面樹中。您只需要使用簡單的 Kotlin 條件邏輯就可以做到這一點。例如,如需顯示初始配置螢幕或問候語列表,您需要執行以下操作:
// Don't copy yet
@Composable
fun MyApp() {
if (shouldShowOnboarding) { // Where does this come from?
OnboardingScreen()
} else {
Greetings()
}
}
但是,我們無法訪問 shouldShowOnboarding
。很明顯,我們需要與 MyApp
可組合項共享在 OnboardingScreen
中建立的狀態。
我們不會以某種方式與狀態的父級共享狀態值,而是會提升該狀態,也就是將該狀態移到需要訪問它的共同祖先實體中。
首先,將 MyApp
的內容移到名為 Greetings
的新可組合項中:
@Composable
fun MyApp() {
Greetings()
}
@Composable
private fun Greetings(names: List<String> = listOf("World", "Compose")) {
Column(modifier = Modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
現在,新增相應的邏輯來顯示 MyApp
中的不同螢幕,並提升狀態。
@Composable
fun MyApp() {
var shouldShowOnboarding by remember { mutableStateOf(true) }
if (shouldShowOnboarding) {
OnboardingScreen(/* TODO */)
} else {
Greetings()
}
}
我們還需要與初始配置螢幕共享 shouldShowOnboarding
,但我們不會直接傳遞它。與其讓 OnboardingScreen
更改狀態,不如讓它在使用者點選“Continue”按鈕時通知我們。
如何向上傳遞事件?通過向下傳遞迴調來傳遞。回撥是這樣一類函式,它們以引數的形式傳遞給其他函式,並在事件發生時執行。
嘗試向初始配置螢幕新增定義為 onContinueClicked: () -> Unit
的函式引數,以便您可以從 MyApp
更改狀態。
解決方案:
@Composable
fun MyApp() {
var shouldShowOnboarding by remember { mutableStateOf(true) }
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
@Composable
fun OnboardingScreen(onContinueClicked: () -> Unit) {
Surface {
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")
}
}
}
}
通過向 OnboardingScreen
傳遞函式而不是狀態,可以提高該可組合項的可重用性,並防止狀態被其他可組合項更改。一般而言,這可以讓事情變得簡單。一個很好的例子就是,現在需要如何修改初始配置螢幕預覽來呼叫 OnboardingScreen
:
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {}) // Do nothing on click.
}
}
將 onContinueClicked
分配給空 lambda 表示式就等於“什麼也不做”,這非常適合於預覽。
看起來已經越來越像一個真正的應用了,非常棒!
到目前為止的完整程式碼:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.ui.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp()
}
}
}
}
@Composable
fun MyApp() {
var shouldShowOnboarding by remember { mutableStateOf(true) }
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
@Composable
fun OnboardingScreen(onContinueClicked: () -> Unit) {
Surface {
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")
}
}
}
}
@Composable
private fun Greetings(names: List<String> = listOf("World", "Compose")) {
Column(modifier = Modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Composable
private fun Greeting(name: String) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greetings()
}
}
建立高效延遲列表
現在,我們來讓名稱列表更真實。到目前為止,您已經在 Column
中顯示了兩條問候語。但是,它可以處理成千上萬條問候語嗎?
將 Greetings
引數中的預設列表值更改為使用其他列表建構函式,這使您可以設定列表的大小並使用其 lambda 中包含的值來填充列表(這裡的 $it
代表列表索引):
names: List<String> = List(1000) { "$it" }
這會建立 1000 條問候語,即使螢幕上放不下這些問候語。顯然,這樣做效果並不好。您可以嘗試在模擬器上執行此程式碼(警告:此程式碼可能會使模擬器卡住)。
為顯示可滾動列,我們需要使用 LazyColumn
。LazyColumn
只會渲染螢幕上可見的內容,從而在渲染大型列表時提升效率。
注意:LazyColumn
和 LazyRow
相當於 Android View 中的 RecyclerView
。
在其基本用法中,LazyColumn
API 會在其作用域內提供一個 items
元素,並在該元素中編寫各項內容的渲染邏輯:
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
...
@Composable
private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
注意:請確保匯入 androidx.compose.foundation.lazy.items
,因為 Android Studio 預設會選擇另一個 items 函式。
注意:LazyColumn
不會像 RecyclerView
一樣回收其子級。它會在您滾動它時發出新的可組合項,並保持高效執行,因為與例項化 Android Views
相比,發出可組合項的成本相對較低。
保留狀態
我們的應用存在一個問題:如果您在裝置上執行該應用,點選按鈕,然後旋轉螢幕,系統會再次顯示初始配置螢幕。remember
函式僅在可組合項包含在組合中時起作用。旋轉屏幕後,整個 activity 都會重啟,所有狀態都將丟失。當發生任何配置更改或者程序終止時,也會出現這種情況。
您可以使用 rememberSaveable
,而不使用 remember
。這會儲存每個在配置更改(如旋轉)和程序終止後保留下來的狀態。
現在,將 shouldShowOnboarding
中的 remember
替換為 rememberSaveable
:
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
執行應用,旋轉螢幕,更改為深色模式,或者終止程序。除非您之前退出了應用,否則系統不會顯示初始配置螢幕。
演示發生配置更改(切換到深色模式)後不會再次顯示初始配置螢幕。
到目前為止,您已經編寫了 120 行左右的程式碼,您可以顯示一個包含大量內容項的高效滾動列表,並且每項內容都有自己的狀態。此外,如您所見,您不需要編寫額外的程式碼就可以讓應用完美呈現深色模式。稍後您將學習主題設定。
為列表新增動畫效果
在 Compose 中,有多種方式可以為介面新增動畫效果:從用於新增簡單動畫的高階 API 到用於實現完全控制和複雜過渡的低階方法,不一而足。您可以在該文件中瞭解相關資訊。
在本部分中,您將使用一個低階 API,但不用擔心,它們也可以非常簡單。下面我們來為已經實現的尺寸變化新增動畫效果:
為此,您將使用 animateDpAsState
可組合項。該可組合項會返回一個 State 物件,該物件的 value
會被動畫持續更新,直到動畫播放完畢。該可組合項需要一個型別為 Dp
的“目標值”。
建立一個依賴於展開狀態的動畫 extraPadding
。此外,我們還需要使用屬性委託(by
關鍵字):
@Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp
)
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
執行應用並檢視該動畫的效果。
注意:如果您展開第 1 項內容,然後滾動到第 20 項內容,再返回到第 1 項內容,您會發現第 1 項內容已恢復為原始尺寸。如果需要,您可以使用 rememberSaveable
儲存此資料,但為了使示例保持簡單,我們不這樣做。
animateDpAsState
接受可選的 animationSpec
引數供您自定義動畫。讓我們來做一些更有趣的嘗試,比如新增基於彈簧的動畫:
@Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
Surface(
...
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
...
)
}
請注意,我們還要確保內邊距不會為負數,否則可能會導致應用崩潰。這會引入一個細微的動畫 bug,我們稍後會在收尾部分進行修復。
spring
規範不接受任何與時間有關的引數。它僅依賴於物理屬性(阻尼和剛度),使動畫更自然。立即執行該應用,檢視新動畫的效果:
使用 animate*AsState
建立的任何動畫都是可中斷的。這意味著,如果目標值在動畫播放過程中發生變化,animate*AsState
會重啟動畫並指向新值。中斷在基於彈簧的動畫中看起來尤其自然:
如果您想探索不同型別的動畫,請嘗試為 spring
提供不同的引數,嘗試使用不同的規範(tween
、repeatable
)和不同的函式(animateColorAsState
或不同型別的動畫 API)。
此部分的完整程式碼
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.ui.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp()
}
}
}
}
@Composable
fun MyApp() {
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
@Composable
fun OnboardingScreen(onContinueClicked: () -> Unit) {
Surface {
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")
}
}
}
}
@Composable
private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greetings()
}
}
設定應用的樣式和主題
到目前為止,您還沒有為任何可組合項設定過樣式,但已經獲得了一個不錯的預設效果,包括支援深色模式!下面我們來了解一下 BasicsCodelabTheme
和 MaterialTheme
。
如果您開啟 ui/Theme.kt
檔案,您會看到 BasicsCodelabTheme
在其實現中使用了 MaterialTheme
:
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = typography,
shapes = shapes,
content = content
)
}
MaterialTheme
是一個可組合函式,體現了 Material Design 規範中的樣式設定原則。樣式設定資訊會逐級向下傳遞到位於其 content
內的元件,這些元件會讀取該資訊來設定自身的樣式。您在介面中已經使用了 BasicsCodelabTheme
,如下所示:
BasicsCodelabTheme {
MyApp()
}
由於 BasicsCodelabTheme
將 MaterialTheme
包圍在其內部,因此 MyApp
會使用該主題中定義的屬性來設定樣式。從任何後代可組合項中都可以檢索 MaterialTheme
的三個屬性:colors
、typography
和 shapes
。使用它們設定其中一個 Text
的標題樣式:
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name, style = MaterialTheme.typography.h4)
}
上例中的 Text
可組合項會設定新的 TextStyle
。您可以建立自己的 TextStyle
,也可以使用 MaterialTheme.typography
檢索由主題定義的樣式(首選)。此結構支援您訪問由 Material 定義的文字樣式,例如 h1
-h6
、body1,body2
、caption
、subtitle1
等。在本例中,您將使用主題中定義的 h4
樣式。
下面我們構建應用來檢視採用新樣式的文字:
通常來說,最好是將顏色、形狀和字型樣式放在 MaterialTheme
中。例如,如果對顏色進行硬編碼,將會很難實現深色模式,並且需要進行大量修正工作,而這很容易造成錯誤。
不過,有時除了選擇顏色和字型樣式,您還可以基於現有的顏色或樣式進行設定。
為此,您可以使用 copy
函式修改預定義的樣式。將數字加粗:
Text(
text = name,
style = MaterialTheme.typography.h4.copy(
fontWeight = FontWeight.ExtraBold
)
)
這樣一來,如果您需要更改 h4
的字體系列或其他任何屬性,就不必擔心出現細微偏差了。
現在,預覽視窗中的結果應如下所示:
調整應用的主題
您可以在 ui
資料夾內的檔案中找到與當前主題相關的所有內容。例如,我們到目前為止所使用的預設顏色均在 Color.kt
中定義。
首先,我們來定義新的顏色。將以下程式碼新增到 Color.kt
中:
val Navy = Color(0xFF073042)
val Blue = Color(0xFF4285F4)
val LightBlue = Color(0xFFD7EFFE)
val Chartreuse = Color(0xFFEFF7CF)
現在,將這些顏色分配給 Theme.kt
中的 MaterialTheme
的調色盤:
private val LightColorPalette = lightColors(
surface = Blue,
onSurface = Color.White,
primary = LightBlue,
onPrimary = Navy
)
返回 MainActivity.kt
並重新整理預覽,就會看到這些新顏色:
但您還未修改深色。在進行這項操作之前,我們需要先設定相應的預覽。使用 UI_MODE_NIGHT_YES
向 DefaultPreview
新增額外的 @Preview
註解:
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "DefaultPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greetings()
}
}
系統隨即會新增一個深色模式的預覽。
在 Theme.kt
中,定義針對深色的調色盤:
private val DarkColorPalette = darkColors(
surface = Blue,
onSurface = Navy,
primary = Navy,
onPrimary = Chartreuse
)
現在,我們已經為應用設定了主題和樣式!
Theme.kt
的最終程式碼
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
private val DarkColorPalette = darkColors(
surface = Blue,
onSurface = Navy,
primary = Navy,
onPrimary = Chartreuse
)
private val LightColorPalette = lightColors(
surface = Blue,
onSurface = Color.White,
primary = LightBlue,
onPrimary = Navy
)
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = typography,
shapes = shapes,
content = content
)
}
結尾
在此步驟中,您將實際運用已掌握的知識,並通過幾條提示來學習幾個新的概念。您將建立以下內容:
用圖示替換按鈕
- 將
IconButton
可組合項與子級Icon
結合使用。 - 使用
material-icons-extended
工件中提供的Icons.Filled.ExpandLess
和Icons.Filled.ExpandMore
。將以下程式碼行新增到app/build.gradle
檔案中的依賴項中。
implementation "androidx.compose.material:material-icons-extended:$compose_version"
- 修改內邊距以修正對齊問題。
- 為無障礙功能新增內容說明(請參閱下面的“使用字串資源”)。
使用字串資源
應該為“Show more”和“show less”提供內容說明,您可以通過簡單的 if
語句進行新增:
contentDescription = if (expanded) "Show less" else "Show more"
不過,硬編碼字串的方式並不可取,應該從 strings.xml
檔案中獲取字串。
您可以通過對每個字串使用“Extract string resource”(在 Android Studio 中的“Context Actions”中提供)來自動執行此操作。
或者,開啟 app/src/res/values/strings.xml
並新增以下資源:
<string name="show_less">Show less</string><string name="show_more">Show more</string>
展開
“Composem ipsum”文字會出現又消失,這會觸發每張卡片的大小變化。
- 將新的
Text
新增到Greeting
中當內容展開時顯示的 Column 中。 - 移除
extraPadding
並改為將animateContentSize
修飾符應用於Row
。這會自動執行建立動畫的過程,而手動執行該過程會很困難。此外,也不需要再使用coerceAtLeast
。
新增高度和形狀
- 您可以結合使用
shadow
修飾符和clip
修飾符來實現卡片外觀。不過,有一種 Material 可組合項也可以做到這一點:Card
。
最終程式碼
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons.Filled
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.R
import com.codelab.basics.ui.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp()
}
}
}
}
@Composable
private fun MyApp() {
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
@Composable
private fun OnboardingScreen(onContinueClicked: () -> Unit) {
Surface {
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")
}
}
}
}
@Composable
private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
@Composable
private fun Greeting(name: String) {
Card(
backgroundColor = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
CardContent(name)
}
}
@Composable
private fun CardContent(name: String) {
var expanded by remember { mutableStateOf(false) }
Row(
modifier = Modifier
.padding(12.dp)
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
) {
Column(
modifier = Modifier
.weight(1f)
.padding(12.dp)
) {
Text(text = "Hello, ")
Text(
text = name,
style = MaterialTheme.typography.h4.copy(
fontWeight = FontWeight.ExtraBold
)
)
if (expanded) {
Text(
text = ("Composem ipsum color sit lazy, " +
"padding theme elit, sed do bouncy. ").repeat(4),
)
}
}
IconButton(onClick = { expanded = !expanded }) {
Icon(
imageVector = if (expanded) Filled.ExpandLess else Filled.ExpandMore,
contentDescription = if (expanded) {
stringResource(R.string.show_less)
} else {
stringResource(R.string.show_more)
}
)
}
}
}
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "DefaultPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
以上就是 Compose 的全部基礎知識了,如果想要繼續進階 Android的Jetpack Compose 技術,或者想學習更多Android前沿技術可以私信《Android核心進階技術手冊》獲取進行進階學習。
文末
學到這裡,那麼恭喜你Compose 的全部基礎知識你已經全部掌握,如有許多沒學會的,可以收藏此篇文章,或者點選上方學習筆記。可以方便重複學習。
啟動新的 Compose 專案
如需啟動新的 Compose 專案,請開啟 Android Studio,然後選擇 Start a new Android Studio project,如下所示:
如果系統未顯示上述介面,請依次進入 File > New > New Project。
建立新專案時,請從可用模板中選擇 Empty Compose Activity。
點選 Next,然後照常配置專案,並將其命名為 Basics Codelab。請確保您選擇的 **minimumSdkVersion 至少為 API 級別 21,這是 Compose 支援的最低 API 級別。
注意:如需詳細瞭解如何使用空 activity 設定 Compose,或如何將其新增到現有專案,可以新增前面資料手冊瞭解詳情。
選擇 Empty Compose Activity 模板後,會在專案中為您生成以下程式碼:
- 該專案已配置為使用 Compose。
- 已建立
AndroidManifest.xml
檔案。 build.gradle
和app/build.gradle
檔案包含 Compose 所需的選項和依賴項。
同步專案後,請開啟 MainActivity.kt
並檢視程式碼。
``` class MainActivity : ComponentActivity() { 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.colors.background ) { Greeting("Android") } } } } }
@Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
@Preview(showBackground = true) @Composable fun DefaultPreview() { BasicsCodelabTheme { Greeting("Android") } } ```
警告:setContent
中使用的應用主題取決於專案名稱。本 Codelab 假定專案名稱為 LayoutsCodelab。**如果您從 Codelab 中複製並貼上程式碼,請記得使用 ui/Theme.kt
檔案中提供的主題名稱來更新 BasicsCodelabTheme
。本 Codelab 後面會講到如何設定主題。
- Android效能優化——記憶體洩漏的根本原因
- Android ViewStub的使用方法——邊走邊看邊學
- Android進階——sdk開發和apk開發有什麼區別?
- Android開發——RXBinding防抖機制與案件分析
- Android效能啟動優化——IO優化進階
- Android適配【入坑指南 解決痛點】
- android 開發——疑難雜症ANR簡單介紹與解析
- Android外掛化框架—— Atlas
- 一名合格的音影片工程師,技能樹狀分佈是怎樣形成的?
- Android核心技術—核心(Linux) 的IO棧
- Android前沿技術—— Jetpack Compose
- Android開發資料結構與演算法——ArrayList原始碼講解
- Flutter中如何構建顯式動畫 【教學】
- Android記憶體抖動(主要原因分析 6個優化小技巧)
- Android車載多媒體開發——MediaSession框架
- 車機空調系統開發(HVAC),溫暖一整個冬天!
- 大廠為什麼在招聘安卓架構師時,為啥都需要熟悉 framework 經驗?
- 一個擴充套件性極強的 Flutter MVVM 實用框架,完善你的技術棧
- 2021年,跨端是否已成趨勢?Android 開發還有必要學 Flutter 嗎?
- Jetpack的MVVM通訊 - LiveData的原理分析