教學課程

Jetpack Compose 教學課程

Jetpack Compose 是建構原生 Android UI 的新型工具包。Jetpack Compose 能以較少的程式碼、強大的工具和符合直覺的 Kotlin API,簡化並加快 Android 平台的 UI 開發作業。

在此教學課程中,您將建構具有宣告函式的簡易 UI 元件。過程中不需修改任何 XML 版面配置,也不必使用版面配置編輯器。您只要呼叫可組合函式來定義所需元素,Compose 編譯器即會完成其餘工作。

全文預覽
全文預覽

第 1 課:可組合函式

Jetpack Compose 是以可組合函式為基礎建構而成。這些函式可讓您以程式輔助的方式定義應用程式 UI,只需描述 UI 的外觀並提供資料依附元件,而不必專注於 UI 的建構過程 (初始化元素、將元素附加至父項等)。如要建立可組合函式,只要在函式名稱中加入 @Composable 註解即可。

新增文字元素

首先,請下載最新版 Android Studio,並透過以下方法建立應用程式:選取「New Project」,接著在「Phone and Tablet」類別下方選取「Empty Activity」。 將應用程式命名為「ComposeTutorial」,然後按一下「Finish」(完成)。預設範本已包含部分 Compose 元素,但在本教學課程中,您將逐步進行建構。

首先,在 onCreate 方法中新增文字元素,讓系統顯示「Hello World!」文字。具體做法是,定義內容區塊,然後呼叫 Text 可組合函式。setContent 區塊會定義活動的版面配置,系統會在其中呼叫可組合函式。可組合函式只能從其他可組合函式呼叫。

Jetpack Compose 使用 Kotlin 編譯器外掛程式,將這些可組合函式轉換為應用程式的 UI 元素。舉例來說,由 Compose UI 程式庫定義的 Text 可組合函式會在螢幕上顯示文字標籤。

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
顯示預覽
隱藏預覽

定義可組合函式

如要讓函式組成,請新增 @Composable 註解。如要試用此功能,請定義已傳遞名稱的 MessageCard 函式,並用該函式設定文字元素。

// ...
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

  
顯示預覽
隱藏預覽

在 Android Studio 中預覽函式

@Preview 註解可讓您在 Android Studio 中預覽可組合函式,無需建構應用程式並將其安裝到 Android 裝置或模擬器中。這個註解必須用於不接受使用參數的可組合函式。因此,您無法直接預覽 MessageCard 函式,而是必須建立另一個名為 PreviewMessageCard 的函式,並由這個函式使用適當參數呼叫 MessageCard。在 @Composable 前加上 @Preview 註解。

// ...
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
顯示預覽
隱藏預覽

重新建構您的專案。應用程式本身不會改變,因為新的 PreviewMessageCard 函式未在任何位置被呼叫,但 Android Studio 會新增一個預覽視窗,只要按一下分割 (設計/程式碼) 檢視畫面即可展開視窗。這個視窗會顯示 UI 元素預覽,這些元素由標記有 @Preview 註解的可組合函式建立。如要隨時更新預覽,請按一下預覽視窗頂端的重新整理按鈕。

在 Android Studio 中預覽可組合函式
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
顯示預覽
隱藏預覽
// ...
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

  
顯示預覽
隱藏預覽
// ...
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
顯示預覽
隱藏預覽
在 Android Studio 中預覽可組合函式

第 2 課:版面配置

UI 元素為階層式,包含其他元素中的元素。在 Compose 中,您可以從其他可組合函式呼叫可組合函式,藉此建構 UI 階層。

新增多個文字

到目前為止,您已建構第一個可組合函式並進行預覽!為了探索更多 Jetpack Compose 功能,我們會建構簡單的訊息螢幕,並在上面顯示可以一些動畫效果展開的訊息清單。

首先,顯示寄件者和訊息內容,讓訊息可組合項更豐富。您必須先將可組合項參數變更為接受 Message 物件 (而非 String),並在 MessageCard 中新增另一個 Text 可組合項。請務必一併更新預覽畫面。

// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
顯示預覽
隱藏預覽

此程式碼會在內容檢視畫面中建立兩個文字元素。不過,由於您並沒有提供任何排列方式的相關資訊,因此這兩個文字元素會彼此重疊,導致文字無法閱讀。

使用 Column

Column 函式可讓您垂直排列元素。將 Column 新增至 MessageCard 函式。
您可以使用 Row 水平排列項目,並使用 Box 堆疊元素。

// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

顯示預覽
隱藏預覽

新增圖片元素

新增寄件者的個人資料相片,讓訊息內容更豐富。使用 Resource Manager 相片庫匯入圖片,或使用這張圖片。新增 Row 可組合項以獲得良好的設計結構,並在這個可組合項中新增 Image 可組合項。

// ...
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
顯示預覽
隱藏預覽

設定版面配置

我們的訊息版面配置雖然結構適當,但其元素之間的間距不合理,且圖片太大!為了裝飾或設定可組合項,Compose 使用修飾符。此功能可變更可組合項的大小、版面配置、外觀或新增高階互動,例如使元素具有可點擊屬性。您可以為這些修飾符建立鏈結,產生功能更豐富的可組合項。您將使用其中一些修飾符來改善版面配置。

// ...
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
顯示預覽
隱藏預覽
// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
顯示預覽
隱藏預覽
預覽兩個重疊的文字可組合項
// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

顯示預覽
隱藏預覽
// ...
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
顯示預覽
隱藏預覽
// ...
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
顯示預覽
隱藏預覽

第 3 課:Material Design

Compose 的設計宗旨是支援質感設計的原則。許多 UI 元素都可以直接實作質感設計。在本課程中,您將學習如何使用質感設計小工具來設定應用程式樣式。

使用質感設計

訊息設計現在支援版面配置,但使用體驗尚有待改進。

Jetpack Compose 可讓您直接實作質感設計 3 及其 UI 元素。您將會使用質感設計樣式來改善 MessageCard 可組合項的外觀。

首先,使用您在專案 ComposeTutorialTheme 中建立的質感主題包裝 MessageCard 函式及 Surface。請務必同時在 @PreviewsetContent 函式中進行這項操作。如此可確保您的可組合項可以沿用應用程式主題中定義的樣式,進而確保在整個應用程式中保持一致。

質感設計以 ColorTypographyShape 三大元素為建構基礎。 您需要逐一新增這些元素。

注意:空白 Compose 活動範本會為專案產生預設主題,以讓您自訂 MaterialTheme。如果您為專案指定的名稱不是 ComposeTutorial,可以在 ui.theme 子套件的 Theme.kt 檔案中找到自訂主題。

// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


  
顯示預覽
隱藏預覽

顏色

透過 MaterialTheme.colorScheme 使用已包裝主題中的顏色設定樣式。您可以在需要顏色的任意位置使用主題中的這些值。本範例使用動態主題設定顏色 (由裝置偏好設定所定義)。 您可以在 MaterialTheme.kt 檔案中將 dynamicColor 設為 false,藉此變更這項設定。

設定標題樣式,並為圖片加上邊框。

// ...
import androidx.compose.foundation.border
import androidx.compose.material3.MaterialTheme

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
顯示預覽
隱藏預覽

字體排版

MaterialTheme 中提供質感字體排版樣式,只要將這些樣式新增至 Text 可組合項即可。

// ...

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.bodyMedium
           )
       }
   }
}

  
顯示預覽
隱藏預覽

形狀

透過 Shape,您就可以新增最後的點睛之筆。首先,將訊息內文文字包裝在 Surface 可組合項中。如此即可自訂訊息內文的形狀和高度。此外,還要為訊息新增邊框間距,以改善版面配置。

// ...
import androidx.compose.material3.Surface

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

  
顯示預覽
隱藏預覽

啟用深色主題

您可以啟用深色主題 (或夜間模式),避免夜間螢幕顯示亮度過強,或單純用來節省裝置電量。支援質感設計後,Jetpack Compose 預設可以處理深色主題。使用質感設計的顏色、文字和背景會自動適應深色背景。

您可以在檔案中建立多個預覽做為個別函式,或新增多個註解至相同函式。

新增預覽註解並啟用夜間模式。

// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
顯示預覽
隱藏預覽

IDE 產生的 Theme.kt 檔案會定義淺色和深色主題的顏色選項。

目前為止,您已建立一個訊息 UI 元素,它可以不同樣式顯示一張圖片和兩種文字,並且在淺色和深色主題下都有良好的視覺效果!

// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
顯示預覽
隱藏預覽
// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    MessageCard(Message("Android", "Jetpack Compose"))
                }
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Surface {
            MessageCard(
                msg = Message("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


  
顯示預覽
隱藏預覽
// ...
import androidx.compose.foundation.border
import androidx.compose.material3.MaterialTheme

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
顯示預覽
隱藏預覽
// ...

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.bodyMedium
           )
       }
   }
}

  
顯示預覽
隱藏預覽
// ...
import androidx.compose.material3.Surface

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

  
顯示預覽
隱藏預覽
// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
顯示預覽
隱藏預覽
預覽畫面同時顯示淺色和深色主題的可組合項。

第 4 課:清單和動畫

清單和動畫會出現在應用程式中的任何地方。在本課程中,您將瞭解如何運用 Compose 輕鬆建立清單,並增添動畫效果。

建立訊息清單

只顯示一則訊息會感覺有點孤單,因此我們可以變更對話內容以便設定多則訊息。您必須建立 Conversation 函式,以顯示多則訊息。針對這個用途,請使用 Compose 的 LazyColumn LazyRow。這些可組合項只會轉譯螢幕上可見的元素,因此對於較長的清單來說,使用這些可組合項可以得到非常優良的效果。

在這個程式碼片段中,您可以看到 LazyColumn 含有一個子項 items。它接受 List 做為參數,而其 lambda 會收到我們命名為 message 的參數 (可以隨意為其命名),也就是 Message 的執行個體。 簡單來說,系統會針對所提供 List 中的每個項目呼叫這個 lambda。將範例資料集複製到專案,即可快速啟動對話。

// ...
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
顯示預覽
隱藏預覽

展開訊息時顯示動畫效果

對話變得更有趣了。現在是時候製作動畫了!您將新增展開訊息以顯示更多內容的功能,同時為內容大小和背景顏色製作動畫效果。如要儲存此本機 UI 狀態,您必須時刻關注訊息是否已展開。您必須使用 remembermutableStateOf 函式來追蹤這種狀態變化。

可組合函式可以使用 remember 將本機狀態儲存在記憶體中,並追蹤傳遞給 mutableStateOf 的值的變化。該值更新時,系統會自動重新繪製使用這個狀態的可組合項 (及其子項)。這就稱為重組

使用 Compose 的狀態 API (例如 remembermutableStateOf),任何狀態變更都會自動更新 UI。

注意:您必須新增下列匯入項目,才能正確使用 Kotlin 的委派屬性語法 (by 關鍵字)。按 Alt + Enter 或 Option + Enter 鍵,即可新增這些項目。
import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue

// ...
import androidx.compose.foundation.clickable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
顯示預覽
隱藏預覽

現在起,當按一下訊息時,即可根據 isExpanded 變更訊息內容的背景。您將使用 clickable 修飾符來處理可組合項中的點擊事件。如今不需要再切換 Surface 的背景顏色,而是逐步將其值從 MaterialTheme.colorScheme.surface 變更為 MaterialTheme.colorScheme.primary (反之亦然),即可以動畫方式呈現背景顏色。為此,您將使用 animateColorAsState 函式。最後,您將使用 animateContentSize 修飾符,流暢地為訊息容器大小製作動畫:

// ...
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
顯示預覽
隱藏預覽
// ...
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
顯示預覽
隱藏預覽
// ...
import androidx.compose.foundation.clickable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
顯示預覽
隱藏預覽
// ...
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

  
顯示預覽
隱藏預覽

後續步驟

恭喜,您已完成 Compose 教學課程!您只用了不到 100 行程式碼就建構了簡易即時通訊螢幕,非常有效率!畫面上展示的動畫訊息清單可供展開,包含圖片和文字,採用質感設計原則和深色主題,並具備預覽功能。

您目前已瞭解的內容如下:

  • 定義可組合函式
  • 在組合中新增不同元素
  • 使用版面配置元件建構 UI 元件
  • 使用修飾詞擴充可組合項
  • 建立高效率清單
  • 持續追蹤並修改狀態
  • 在可組合項上新增使用者互動
  • 在展開時為訊息加入動畫效果

如要深入瞭解這些步驟,請參閱下列資源。

後續步驟

設定
您已完成 Compose 教學課程,可開始使用 Compose 進行建構。
課程
歡迎查看我們專為程式碼研究室和影片提供的一系列課程,可協助您學習及精進 Jetpack Compose。