教學課程

Jetpack Compose 教學課程

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

在此教學課程中,您將建構具有宣告函式的簡易 UI 元件。您不會編輯任何 XML 版面配置或使用版面配置編輯器。相反地,您必須呼叫 Jetpack Compose 函式來表示您想要使用哪些元素,而 Compose 編譯器將會完成其餘作業。

全文預覽
全文預覽

第 1 課:可組合函式

Jetpack Compose 是針對可組合函式所建構。這些函式可讓您以程式輔助的方式定義應用程式 UI,方法是說明其樣式及提供資料依附元件,而不是專注於 UI 的建構流程(初始化元素、將其附加至父項等)。如要建立可組合函式,只要在函式名稱中加入 @Composable 註解即可。

新增文字元素

請先下載最新版的 Android Studio,然後使用空白 Compose 活動範本建立應用程式。預設範本已包含一些 Compose 元素,不過我們還是進行逐步建立。

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

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

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

定義可組合函式

如要讓函式組成,請新增 @Composable 註解。如要試用這項功能,請定義傳送名稱的 MessageCard() 函式,使用此函式來設定文字元素。

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 中預覽函式

Android Studio 可讓您在 IDE 中預覽可組合函式,而不必將應用程式安裝至 Android 裝置或模擬器。可組合函式必須提供所有參數的預設值。因此,您無法直接預覽 MessageCard() 函式。我們來再使用名為 PreviewMessageCard() 的第二個函式,並使用適當的參數呼叫 MessageCard()。在 @Composable 前加上 @Preview 註解。

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

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

重建專案應用程式本身不會變更,因為新的 PreviewMessageCard() 函式不會在任何位置呼叫,但 Android Studio 會新增預覽視窗。這個視窗會顯示 UI 元素預覽,它由可組合函式建立,標記為 @Preview 註解。如要隨時更新預覽,請按一下預覽視窗頂端的重新整理按鈕。

圖 1。使用 Android Studio 預覽可組合函式。
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
顯示預覽
隱藏預覽
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

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

  
顯示預覽
隱藏預覽
@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
顯示預覽
隱藏預覽
圖 1。使用 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("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
顯示預覽
隱藏預覽

此程式碼會在內容檢視畫面中建立兩個文字元素。不過,由於我們並沒有提供任何排列方式的相關資訊,因此文字元素會彼此繪製,讓文字無法讀取。

使欄

Column 函式可讓您垂直排列元素。將 Column 新增至 MessageCard() 函式。
使用列可水平排列項目,方塊則可堆疊元素。

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

顯示預覽
隱藏預覽

新增圖片元素

新增寄件者的個人資料相片,讓訊息更加豐富。使用 Resource Manager 匯入相片庫的相片,或使用這張圖片。新增 Row 可組合元素以獲得結構良好的設計,其中必須含有 Image 可組合元素:

@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 會使用輔助鍵。此功能可變更可組合元素的大小、版面配置、外觀或新增高階互動元素,例如將元素設為可點擊屬性。可以將這些元素鏈結以產生更為豐富的可組合元素。讓我們使用其中幾個來改善版面配置:

@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("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
顯示預覽
隱藏預覽
@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

顯示預覽
隱藏預覽
@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)
        }
  
    }
  
}
  
顯示預覽
隱藏預覽
@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 課:質感設計

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

使用質感設計

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

Jetpack Compose 提供質感設計及其 UI 元素,可立即使用。我們會運用質感設計樣式,改善 MessageCard 的可組合元素的外觀。

首先,請將 MessageCard 函式及在專案中建立的質感主題 ComposeTutorialTheme 進行包裝。請使用 @PreviewsetContent 函式進行此操作。

質感設計是圍繞三大支柱建構而成:顏色、字型、形狀。讓我們逐一新增

注意:空白 Compose 活動會產生專案的預設主題,讓您自訂 MaterialTheme。如果您將專案命名為 ComposeTutorial 之外的名稱,則可在 ui.theme 套件中找到您的自訂主題。

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

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

  
顯示預覽
隱藏預覽

顏色

使用封裝主題的顏色進行樣式設定很簡單,您可直接使用主題中的值進行顏色設定。

設定標題樣式並在圖片中加入框線:

@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.colors.secondary, CircleShape)
       )

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

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant
           )

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

  
顯示預覽
隱藏預覽

字體排版

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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

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

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

  
顯示預覽
隱藏預覽

形狀

可以使用形狀加入最終輕觸。我們也在訊息中加上邊框間距,讓版面配置更加完善。

@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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

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

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

  
顯示預覽
隱藏預覽

啟用深色主題

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

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

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

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

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

目前為止,我們建立了一個訊息 UI 元素,可以顯示一張圖片和兩則不同樣式的文字,在淺色和深色主題中都沒有問題!

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
       MessageCard(
           msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
       )
   }
}
  
顯示預覽
隱藏預覽
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                MessageCard(Message("Android", "Jetpack Compose"))
            }
        }
    }
}

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

  
顯示預覽
隱藏預覽
@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.colors.secondary, CircleShape)
       )

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

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant
           )

           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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

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

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

  
顯示預覽
隱藏預覽
@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.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

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

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

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

第 4 課:清單和動畫

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

建立訊息清單

只傳送一則訊息會感覺有點孤單,因此我們可以變更對話內容以便設定多則訊息。我們必須建立顯示多則訊息的 Conversation 函式。以這個用途來說,我們可以使用 Compose 的 LazyColumnLazyRow. 這些可組合元素只會呈現在螢幕上顯示的元素,因此非常適合用於長清單。與此同時,也能避免使用 XML 版面配置的 RecyclerView 時的複雜度。

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

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。

注意:必須新增下列匯入項目,才能正確使用 by。使用 Alt + Enter 或 Option + Enter 鍵即可新增項目。

import androidx.compose.runtime.getValue 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.colors.secondaryVariant, 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.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

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

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
顯示預覽
隱藏預覽

現在起,當按一下訊息時,即可以根據 isExpanded 變更訊息內容的背景。我們會使用 clickable 輔助鍵來處理可組合元素的點擊事件。如今不需要再切換 Surface 的背景顏色,而是會逐步將背景的值從 MaterialTheme.colors.surface 變更為 MaterialTheme.colors.primary(反之亦然),以動畫方式呈現背景顏色。為此,我們會使用 animateColorAsState 函式。最後,我們將使用 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.colors.secondaryVariant, 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: Color by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
        )

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

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

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
顯示預覽
隱藏預覽
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)
    }
}

  
顯示預覽
隱藏預覽
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.colors.secondaryVariant, 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.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

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

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
顯示預覽
隱藏預覽
@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.colors.secondaryVariant, 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: Color by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
        )

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

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

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 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.body2
                )
            }
        }
    }
}

  
顯示預覽
隱藏預覽

後續步驟

恭喜,您已完成 Compose 教學課程!你建立了簡潔的即時通訊螢幕,並在其中提供包含圖片和文字的可展開及動畫訊息清單,採用質感設計原則及深色主題,可供預覽,一切只需要不到 100 行程式碼!

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

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

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

繼續學習

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