新增可捲動的清單

1. 事前準備

在這個程式碼研究室中,您將瞭解如何使用 Jetpack Compose 在應用程式中建立可捲動的清單。

您將使用 Affirmations 應用程式,此應用程式會顯示肯定字詞清單並搭配美麗圖片,為您的一天帶來正能量!

我們已有資料,您只需擷取相關資料並在 UI 中顯示即可。

必要條件

  • 熟悉 Kotlin 中的清單
  • 有使用 Jetpack Compose 建構版面配置的經驗
  • 瞭解如何使用裝置或模擬器執行應用程式

課程內容

  • 如何使用 Jetpack Compose 建立 Material Design 資訊卡
  • 如何使用 Jetpack Compose 建立可捲動的清單

建構項目

  • 您將使用現有的應用程式,在 UI 中加入可捲動的清單

成品會如下所示:

286f5132aa155fa6.png

軟硬體需求

  • 可連上網路、具備網路瀏覽器且已安裝 Android Studio 的電腦
  • GitHub 存取權

下載範例程式碼

在 Android Studio 中開啟 basic-android-kotlin-compose-training-affirmations 資料夾。

使用 starter 分支版本程式碼建構時,應用程式應會顯示空白畫面。

3beea0789e2eeaba.png

2. 建立清單項目資料類別

建立 Affirmation 資料類別

在 Android 應用程式中,清單是由清單項目組成。如果是單一資料,這可以是簡單的字串或整數。如果是有多個資料的清單項目 (例如圖片和文字),您就需要一個包含所有屬性的類別。資料類別是只包含屬性的類別類型,可以提供適用於這些屬性的公用程式方法。

  1. com.example.affirmations 下建立新套件。

89c8d8485c685fac.png

將新套件命名為「Model」。Model 套件會包含由資料類別代表的資料模型。資料類別所含的屬性會代表與「Affirmation」相關的資訊,並由字串資源和圖片資源組成。套件是包含類別的目錄,甚至可能包含其他目錄。

b54fb6bf57de44c8.png

  1. com.example.affirmations.model 套件中建立新類別。

58510a651bd49100.png

將新類別命名為「Affirmation」,然後將其設為「Data class」

7f94b65ee3d8407f.png

  1. 每個 Affirmation 都含有一張圖片和一個字串。在 Affirmation 資料類別中建立兩個 val 屬性。其中一個應該稱為 stringResourceId,而另一個則是 imageResourceId。兩個都必須是整數。

Affirmation.kt

data class Affirmation(
    val stringResourceId: Int,
    val imageResourceId: Int
)
  1. 使用 @StringRes 註解為 stringResourceId 屬性加上註解,並使用 @DrawableRes 註解為 imageResourceId 加上註解。stringResourceId 代表字串資源中儲存的肯定文字 ID。imageResourceId 代表可繪製資源中儲存的肯定圖片 ID。

Affirmation.kt

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes

data class Affirmation(
    @StringRes val stringResourceId: Int,
    @DrawableRes val imageResourceId: Int
)
  1. com.example.affirmations.data 套件中,開啟 Datasource.kt 檔案,並取消註解兩個匯入陳述式和 Datasource 類別內容。

Datasource.kt

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource() {
    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1, R.drawable.image1),
            Affirmation(R.string.affirmation2, R.drawable.image2),
            Affirmation(R.string.affirmation3, R.drawable.image3),
            Affirmation(R.string.affirmation4, R.drawable.image4),
            Affirmation(R.string.affirmation5, R.drawable.image5),
            Affirmation(R.string.affirmation6, R.drawable.image6),
            Affirmation(R.string.affirmation7, R.drawable.image7),
            Affirmation(R.string.affirmation8, R.drawable.image8),
            Affirmation(R.string.affirmation9, R.drawable.image9),
            Affirmation(R.string.affirmation10, R.drawable.image10))
    }
}

3. 在應用程式中加入清單

建立清單項目資訊卡

此應用程式的用途是顯示肯定字詞清單。設定 UI 以顯示清單的第一步,就是建立清單項目。每個肯定字詞項目都包含一個圖片和字串。範例程式碼含有這些項目的個別資料,您將建立 UI 元件以顯示此類項目。

項目將包含一個 Card 可組合函式,其中包含 ImageText 可組合函式。在 Compose 中,Card 是在單一容器中顯示內容和動作的介面。Affirmation 資訊卡在預覽畫面中會如下所示:

4f657540712a069f.png

資訊卡顯示的圖片下方有一段文字。使用 Card 可組合函式中納入的 Column 可組合函式,就可達成此直向版面配置。您可以自行嘗試,或是按照以下步驟進行。

  1. 開啟 MainActivity.kt 檔案。
  2. AffirmationsApp() 方法下方建立名為 AffirmationCard() 的新方法,然後加上 @Composable 註解。

MainActivity.kt

@Composable
fun AffirmationsApp() {
}

@Composable
fun AffirmationCard() {

}
  1. 編輯方法簽章,以使用 Affirmation 物件做為參數。Affirmation 物件來自 model 套件。

MainActivity.kt

import com.example.affirmations.model.Affirmation

@Composable
fun AffirmationCard(affirmation: Affirmation) {

}
  1. 在簽章中加入 modifier 參數。設定該參數預設值為 Modifier

MainActivity.kt

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {

}
  1. AffirmationCard 方法內,呼叫 Card 可組合函式。傳入 modifier 參數。

MainActivity.kt

import androidx.compose.material3.Card

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {

    }
}
  1. Card 可組合函式中加入一個 Column 可組合函式。Column 可組合函式內的項目會在 UI 中垂直排列。這可讓您在圖片放置在相關文字上方。相反地,Row 可組合函式會以水平方式排列所含項目。

MainActivity.kt

import androidx.compose.foundation.layout.Column

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {

        }
    }

}
  1. Column 可組合函式的 lambda 內文中加入 Image 可組合函式。提醒您,Image 可組合函式一律需要資源才能顯示,還有 contentDescription。資源應為傳遞至 painter 參數的 painterResourcepainterResource 方法會載入向量可繪項目或柵格化素材資源格式 (例如 PNG)。同時傳遞 stringResource 用於 contentDescription 參數。

MainActivity.kt

import androidx.compose.foundation.Image
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
            )
        }
    }
}
  1. 除了 paintercontentDescription 參數外,還會傳遞 modifiercontentScalecontentScale 可決定圖片的縮放和顯示方式。Modifier 物件應設定 fillMaxWidth 屬性,且高度為 194.dpcontentScale 應為 ContentScale.Crop

MainActivity.kt

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.ui.unit.dp
import androidx.compose.ui.layout.ContentScale

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
                modifier = Modifier
                    .fillMaxWidth()
                    .height(194.dp),
                contentScale = ContentScale.Crop
            )
        }
    }
}
  1. Column 內,於 Image 可組合函式之後建立 Text 可組合函式。將 affirmation.stringResourceIdstringResource 傳遞至 text 參數、傳遞 padding 屬性設定至 16.dpModifier 物件,並傳遞 MaterialTheme.typography.headlineSmallstyle 參數以設定文字主題。

MainActivity.kt

import androidx.compose.material3.Text
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.platform.LocalContext

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
                modifier = Modifier
                    .fillMaxWidth()
                    .height(194.dp),
                contentScale = ContentScale.Crop
            )
            Text(
                text = LocalContext.current.getString(affirmation.stringResourceId),
                modifier = Modifier.padding(16.dp),
                style = MaterialTheme.typography.headlineSmall
            )
        }
    }
}

預覽 AffirmationCard 可組合函式

資訊卡是 Affirmations 應用程式的 UI 核心,也是您努力建立的成果!如要檢查資訊卡是否正確無誤,您可以建立可預覽的可組合函式,不必啟動整個應用程式。

  1. 建立名為 AffirmationCardPreview() 的不公開方法。使用 @Preview@Composable 為方法加上註解。

MainActivity.kt

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

@Preview
@Composable
private fun AffirmationCardPreview() {

}
  1. 在方法內部,呼叫 AffirmationCard 可組合函式並傳入新的 Affirmation 物件,該物件的建構函式中則傳入 R.string.affirmation1 字串資源及 R.drawable.image1 可繪製資源。

MainActivity.kt

@Preview
@Composable
private fun AffirmationCardPreview() {
    AffirmationCard(Affirmation(R.string.affirmation1, R.drawable.image1))
}
  1. 開啟「Split」分頁,即可查看 AffirmationCard 的預覽畫面。如有必要,按一下「Design」窗格中的「Create & Refresh」,即可顯示預覽畫面。

924a4df2c1db236c.png

建立清單

清單項目元件是清單的構成元素。建立清單項目後,就可以利用清單項目製作清單元件。

  1. 建立名為 AffirmationList() 的函式,然後使用 @Composable 註解加上註解,並在方法簽名中宣告 Affirmation 物件的 List 為參數。

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>) {

}
  1. 在方法簽名中宣告 modifier 物件為參數,預設值為 Modifier

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {

}
  1. 在 Jetpack Compose 中,您可以使用 LazyColumn 可組合函式建立可捲動的清單。LazyColumnColumn 的差異,在於要顯示少量項目時應使用 Column,因為 Compose 可以一次載入所有項目。Column 只能保留預先定義或固定的可組合函式。LazyColumn 可視需求加入內容,這對於長清單而言特別實用,尤其在清單長度不明時更是如此。根據預設,LazyColumn 也會提供捲動功能,不需使用其他程式碼。請在 AffirmationList() 函式內宣告 LazyColumn 可組合函式。請將 modifier 物件當做引數,傳遞至 LazyColumn

MainActivity.kt

import androidx.compose.foundation.lazy.LazyColumn

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {

    }
}
  1. LazyColumn 的 lambda 主體中,呼叫 items() 方法,並傳入 affirmationListitems() 方法可讓您在 LazyColumn 中加入項目。這種方法對於此可組合函式而言比較特別,因此並不是大多數可組合函式的常見做法。

MainActivity.kt

import androidx.compose.foundation.lazy.items

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) {

        }
    }
}
  1. 呼叫 items() 方法需要 lambda 函式。在該函式中,指定 affirmation 的參數,代表來自 affirmationList 的其中一個肯定字詞項目。

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) { affirmation ->

        }
    }
}
  1. 為清單中的每個肯定字詞呼叫 AffirmationCard() 可組合函式。使用設為 8.dppadding 屬性,將 affirmationModifier 物件傳入該可組合函式。

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) { affirmation ->
            AffirmationCard(
                affirmation = affirmation,
                modifier = Modifier.padding(8.dp)
            )
        }
    }
}

顯示清單

  1. AffirmationsApp 可組合函式中,擷取目前的版面配置方向,然後儲存在變數中。稍後,這些值會用來設定邊框間距。

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
}
  1. 現在請建立 Surface 可組合函式。這個可組合函式會為 AffirmationsList 可組合函式設定邊框間距。

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface() {
    }
}
  1. Modifier 傳遞至 Surface 可組合函式,填滿其父項的最大寬度和高度、設定狀態列邊框間距,並將開始和結束邊框間距設在 layoutDirection 中。以下範例說明如何將 LayoutDirection 物件轉換為邊框間距:WindowInsets.safeDrawing.asPaddingValues().calculateStartPadding(layoutDirection)

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        Modifier = Modifier
        .fillMaxSize()
        .statusBarsPadding()
        .padding(
            start = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateStartPadding(layoutDirection),
            end = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateEndPadding(layoutDirection),
        ),
    ) {
    }
}
  1. Surface 可組合函式的 lambda 中,呼叫 AffirmationList 可組合函式,然後將 DataSource().loadAffirmations() 傳遞至 affirmationList 參數。

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        Modifier = Modifier
        .fillMaxSize()
        .statusBarsPadding()
        .padding(
            start = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateStartPadding(layoutDirection),
            end = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateEndPadding(layoutDirection),
        ),
    ) {
        AffirmationsList(
            affirmationList = Datasource().loadAffirmations(),
        )
    }
}

在裝置或模擬器上執行 Affirmations 應用程式,即可查看成品!

286f5132aa155fa6.png

4. 取得解決方案程式碼

完成程式碼研究室後,如要下載當中用到的程式碼,您可以使用這些 git 指令:

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-affirmations.git
$ cd basic-android-kotlin-compose-training-affirmations
$ git checkout intermediate

另外,您也可以下載存放區為 ZIP 檔案,然後解壓縮並在 Android Studio 中開啟。

如要查看解決方案程式碼,請前往 GitHub

5. 結語

現在您已經知道如何使用 Jetpack Compose 建立資訊卡、清單項目和可捲動的清單!請注意,以上只是建立清單的基本工具,您可以盡情揮灑創意,自由自訂各種清單項目!

摘要

  • 使用 Card 可組合函式建立清單項目。
  • 修改 Card 可組合函式內含的 UI。
  • 使用 LazyColumn 可組合函式建立可捲動的清單。
  • 使用自訂清單項目建構清單。