Compose 的基本版面配置

1. 簡介

Compose 是 UI 工具包,可讓您輕鬆實現應用程式設計。您可以說明希望 UI 呈現的效果,而 Compose 會負責在螢幕上繪製出來。本程式碼研究室會說明如何編寫 Compose UI。本文假設您已瞭解基本程式碼研究室中說明的概念,因此請務必先完成此程式碼研究室。在 Basics 程式碼研究室中,您已瞭解如何使用 SurfacesRowsColumns 實作簡單的版面配置。此外,您還使用 paddingfillMaxWidthsize 等修飾詞來擴充這些版面配置。

在本程式碼研究室中,您將實作更真實複雜的版面配置,並在過程中瞭解各種立即可用的可組合項修飾符。完成本程式碼研究室後,您應能將基本應用程式的設計轉換成可運作的程式碼。

本程式碼研究室不會在應用程式中新增任何實際行為。如要瞭解狀態和互動,請改為完成有關 Compose 狀態的程式碼研究室

如果您在閱讀本程式碼研究室時需要更多支援,請參閱下列程式碼:

課程內容

在本程式碼研究室,您將學到:

  • 修飾詞如何協助您擴增可組合項。
  • Column 和 LazyRow 等標準版面配置元件如何置放子項可組合項。
  • 對齊和排列方式會如何變更子項可組合項在上層元件中的位置。
  • Scaffold 和底部導覽列等 Material 可組合項如何協助您建立全面的版面配置。
  • 如何使用 Slot API 建構彈性的可組合項。

軟硬體需求

建構項目

在本程式碼研究室中,您將根據設計人員提供的模擬程式碼,實現真實的應用程式設計。MySoothe 是一款身心健康的應用程式,其中列出多種可以改善身心的方法。這個應用程式包含兩個部分,一個列出使用者最愛的收藏,另一個則顯示各種體能運動。應用程式應如下所示:

24ff9efa75f22198.png

2. 開始設定

在這個步驟中,您將下載包含主題設定和一些基本設定的程式碼。

取得程式碼

您可以在 android-compose-codelabs GitHub 存放區中找到本程式碼研究室的程式碼。如要複製該存放區,請執行下列命令:

$ git clone https://github.com/googlecodelabs/android-compose-codelabs

或者,您也可以下載兩個 ZIP 檔案:

查看程式碼

下載的程式碼包含所有可用 Compose 研究室程式碼的程式碼。如要完成本程式碼研究室,請在 Android Studio 中開啟 BasicLayoutsCodelab 專案。

建議您先從 main 分支版本的程式碼著手,依自己的步調逐步完成本程式碼研究室。

3. 先預做規劃

讓我們進一步檢視這裡所採用的設計方式:

c31e78e48cc1f336.png

當系統要求您實作設計時,最好先瞭解其結構。不要直接開始編寫程式碼,而是先分析設計本身。試著思考看看:如何將這個 UI 分成多個可重複使用的部分

一起來設計一下。而在最高抽象層,我們可以將這個設計分成兩個部分:

  • 螢幕的內容。
  • 底部導覽。

9a0f4be94a5a206c.png

往下一層,畫面內容包含三個子部分:

  • 搜尋列。
  • 名為「調整身體狀態」的區段。
  • 名為「最愛的收藏」的部分。

d9bf2ca5a0939959.png

每個章節中也會列出重複使用的低階元件:

  • 顯示在水平捲軸的「調整身體狀態」元素。

29bed1f813622dc.png

  • 「Favorite Collection」資訊卡,顯示在水平捲動的格狀檢視中。

cf1fe8b2d682bfca.png

現在您已經分析了設計,現在可以開始為 UI 的各個可辨識元件實作可組合項。先從最低級別的可組合項開始,然後繼續合併以形成較複雜的可組合項。完成程式碼研究室後,您的新應用程式看起來會像是提供的設計。

4. 搜尋列 - 修飾詞

要轉換成可組合項的第一個元素是搜尋列。接著將說明設計:

6b7c2f913d189b9a.png

單憑這個螢幕截圖,就很難在完美的像素上實作這項設計。一般來說,設計人員可以傳達設計的更多詳細資訊。允許您使用他們的設計工具,或分享所謂的紅線設計。在本例子中,我們的設計人員做出了紅線設計,以用來判斷任何尺寸相關的數值。系統會以 8dp 格線重疊方式顯示設計內容,方便您查看元素之間的空間大小。此外,我們也會明確地加入一些間距,清楚呈現特定大小。

6c6854661a89e995.png

您可以看到搜尋列的高度必須為 56 密度獨立像素。此外,搜尋列應填滿父項的完整寬度。

如要導入搜尋列,請使用名為「文字欄位」的 Material 元件。Compose Material 程式庫包含名為 TextField 的可組合項,就是這個 Material 元件的實作。

請先從基本的 TextField 實作著手。在程式碼集中開啟 MainActivity.kt,並搜尋 SearchBar 可組合項。

在名為 SearchBar 的可組合元件中,編寫基本 TextField 實作:

import androidx.compose.material.TextField

@Composable
fun SearchBar(
   modifier: Modifier = Modifier
) {
   TextField(
       value = "",
       onValueChange = {},
       modifier = modifier
   )
}

注意事項:

  • 您已對文字欄位值進行硬式編碼,onValueChange 回呼則不會執行任何動作。由於這是以版面配置為主的程式碼研究室,因此您會忽略與狀態相關的所有行為。
  • SearchBar 可組合函式接受 modifier 參數,並傳送至 TextField。這是根據「Compose 指南」的最佳做法。如此一來,這個方法的呼叫端就會修改可組合項的外觀與風格,讓可組合項更具彈性,而且可重複使用。您將針對本程式碼研究室中的所有可組合項,繼續採用這項最佳做法。

我們來看看這個可組合函式的預覽畫面。在此提醒,您可以使用 Android Studio 中的預覽功能,快速執行個別可組合項的疊代作業。MainActivity.kt 包含多個預覽內容,分別對應至您將在這個程式碼研究室中建構的每個可組合項。在此情況下,方法 SearchBarPreview 會算繪 SearchBar 的可組合項,帶有一些背景和邊框間距,以提供更多背景資訊。新增實作後看起來會像這樣:

c2e1eec30f36bc72.png

缺少一些事物。首先,讓我們使用修飾詞修正可組合項的大小。

編寫可組合項時,您可以使用修飾詞執行下列操作:

  • 變更可組合項的大小、版面配置、行為和外觀。
  • 新增資訊,例如無障礙標籤。
  • 處理使用者輸入內容
  • 新增高等級互動,例如讓元素可供點選、可捲動、可拖曳或可縮放。

您呼叫的每個可組合項都有 modifier 參數,可設定參數以貼合該可組合項的外觀、風格和行為。設定修飾符時,您可以鏈結多個修飾符方法,以建立更複雜的貼合方式。

在這種情況下,搜尋列的高度至少為 56dp,且會填滿其父項寬度。如要找出適用此項目的修飾詞,請參閱「修飾詞清單」,並參閱「大小部分」。如果是高度,您可以使用 heightIn 修飾詞。確保可組合項有特定的最小高度。但要是使用者放大了系統字型大小,高度便會隨之變高。如果是寬度,您可以使用 fillMaxWidth 修飾詞。這個修飾符可確保搜尋列填滿其父項的所有水平空間。

請更新修飾符,使其符合以下程式碼:

import androidx.compose.material.TextField

@Composable
fun SearchBar(
   modifier: Modifier = Modifier
) {
   TextField(
       value = "",
       onValueChange = {},
       modifier = modifier
           .fillMaxWidth()
           .heightIn(min = 56.dp)
   )
}

在此情況下,由於其中一個修飾符會影響寬度,而另一個修飾符會影響高度,所以這些修飾符的順序並不重要。

您也必須設定 TextField 的部分參數。試著設定參數值,讓可組合項看起來像設計。設計如下:

6b7c2f913d189b9a.png

請按照下列步驟更新實作:

  • 新增搜尋圖示。TextField 包含可接受其他可組合項的參數 leadingIcon。您可以在內部設定 Icon,在本範例中應為 Search 圖示。請務必使用正確的 Compose Icon 匯入功能。
  • 將文字欄位的背景顏色設為 MaterialTheme 的 surface 顏色。您可以使用 TextFieldDefaults.textFieldColors 覆寫特定顏色。
  • 新增預留位置文字「Search」(以字串資源 R.string.placeholder_search 的形式顯示)。

完成後,可組合項看起來會像這樣:

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.ui.res.stringResource
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search

@Composable
fun SearchBar(
   modifier: Modifier = Modifier
) {
   TextField(
       value = "",
       onValueChange = {},
       leadingIcon = {
           Icon(
               imageVector = Icons.Default.Search,
               contentDescription = null
           )
       },
       colors = TextFieldDefaults.textFieldColors(
           backgroundColor = MaterialTheme.colors.surface
       ),
       placeholder = {
           Text(stringResource(R.string.placeholder_search))
       },
       modifier = modifier
           .fillMaxWidth()
           .heightIn(min = 56.dp)
   )
}

請注意:

  • 您已新增顯示搜尋圖示的 leadingIcon。這個圖示不需要內容說明,因為文字欄位的預留位置已說明文字欄位的含義。提醒您,內容說明通常用於無障礙用途,並為使用者提供應用程式的圖片或圖示的文字說明。
  • 如要調整文字欄位的背景顏色,請設定 colors 屬性。可組合項包含一合併參數,而非個別顏色有不同參數。您傳入 TextFieldDefaults 資料類別的副本,只會更新有所不同的顏色。在本範例中,這只是背景顏色。
  • 您必須設定最小高度,而不是固定高度。以下為建議做法,使文字欄位依然可以放大:例如,當使用者在系統設定裡放大字型大小時。

在這個步驟中,您將瞭解如何使用可組合的參數和修飾詞來變更可組合元件的外觀和風格。這適用於 Compose 和 Material 程式庫提供的可組合項,以及您自行編寫的可組合項。您應一律提供參數來自訂撰寫的可組合項。建議您一併新增 modifier 屬性,以便從外部調整可組合的外觀和風格。

5. 調整身體狀態 - 對齊

您將實作的下一個可組合項為「調整身體狀態」元素。我們來看看其設計,包括它旁邊的紅線設計:

29bed1f813622dc.png 9d11e16a8817686f.png

紅線設計現在也包括以基準線為標準的間距。我們從中取得的資訊如下:

  • 圖片高度應為 88dp。
  • 文字的基準線與圖片之間的間距應為 24dp。
  • 基準線和元素底部之間的間距應為 8dp。
  • 文字應採用 H3 字體排版。

如要實作這個可組合項,您需要有 ImageText 可組合項。它們必須包含在 Column 中,因此位於彼此下方。

請在程式碼中找出 AlignYourBodyElement 可組合項,並根據這個基本實作更新可組合項內容:

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.ui.res.painterResource

@Composable
fun AlignYourBodyElement(
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
   ) {
       Image(
           painter = painterResource(R.drawable.ab1_inversions),
           contentDescription = null
       )
       Text(
           text = stringResource(R.string.ab1_inversions)
       )
   }
}

請注意:

  • 將圖像的 contentDescription 設為空值,因為此圖片只是裝飾性。圖片下方的文字足以充分描述意義,因此不需要為圖片提供額外的說明。
  • 您目前使用硬式編碼的圖片和文字。在下一個步驟中,您將使用 AlightYourBodyElement 可組合項中提供的參數來動態調整。

請看看這個可組合項的預覽畫面:

b9686f83eb73c542.png

還有幾項改善措施。最值得注意的是,圖片太大且形狀並非圓形。您可以使用 sizeclip 修飾詞和 contentScale 參數來調整 Image 可組合項。

與上一個步驟中的 fillMaxWidthheightIn 修飾符類似,size 修飾符會調整可組合項,使其符合特定大小。clip 修飾詞的運作方式不同,而且會調整可組合項的外觀。您可以將其設為任何 Shape,並將其可組合項的內容剪輯成形狀。

圖片也需要正確縮放。為此,我們可以使用 ImagecontentScale 參數。選項主要包括:

5f17f07fcd0f1dc.png

在這種情況下,裁剪類型是正確的使用類型。套用修飾詞和參數後,程式碼應如下所示:

import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
@Composable
fun AlignYourBodyElement(
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
   ) {
       Image(
           painter = painterResource(R.drawable.ab1_inversions),
           contentDescription = null,
           contentScale = ContentScale.Crop,
           modifier = Modifier
               .size(88.dp)
               .clip(CircleShape)
       )
       Text(
           text = stringResource(R.string.ab1_inversions)
       )
   }
}

您的設計現在看起來應像這樣:

6576ed1e8b1cde30.png

下一步是設定 Column 的對齊方式,以水平對齊文字。

一般來說,如要對齊上層容器中的可組合項,請設定該父項容器的對齊方式。因此,與其讓子項自行定位父項的位置,不如告訴父項如何對齊子項。

針對 Column,您可以決定其子項的水平對齊方式。可採用的選項包括:

  • 開始
  • CenterHorizontally
  • 結尾

針對 Row,您需要設定垂直對齊。選項與 Column 中的選項類似:

  • 正上方
  • CenterVertically
  • 底部

針對 Box,您可以合併水平和垂直對齊方式。可採用的選項包括:

  • TopStart
  • TopCenter
  • 頂端結尾
  • CenterStart
  • 置中
  • CenterEnd
  • BottomStart
  • BottomCenter
  • BottomEnd

容器的所有子項都會採用相同的對齊模式。您可以在子項中新增 align 修飾詞,藉此覆寫單一子項的行為。

在這項設計中,文字應水平置中。方法是將 ColumnhorizontalAlignment 設為水平置中:

import androidx.compose.ui.Alignment
@Composable
fun AlignYourBodyElement(
   modifier: Modifier = Modifier
) {
   Column(
       horizontalAlignment = Alignment.CenterHorizontally,
       modifier = modifier
   ) {
       Image(
           //..
       )
       Text(
           //..
       )
   }
}

實作這些部分後,您只需要進行幾項小幅變更,讓可組合項與設計保持一致。如果遇到問題,可以嘗試自行實作程式碼,也可以參照最終程式碼。建議您採取下列步驟:

  • 讓圖片和文字保持動態。並將其做為引數傳遞至可組合函式。請務必更新對應的預覽,並傳遞一些硬式編碼的資料。
  • 更新文字以使用正確的字型樣式。
  • 更新文字元素的基準間距。

完成這些步驟後,您的程式碼應如下所示:

import androidx.compose.foundation.layout.paddingFromBaseline

@Composable
fun AlignYourBodyElement(
   @DrawableRes drawable: Int,
   @StringRes text: Int,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier,
       horizontalAlignment = Alignment.CenterHorizontally
   ) {
       Image(
           painter = painterResource(drawable),
           contentDescription = null,
           contentScale = ContentScale.Crop,
           modifier = Modifier
               .size(88.dp)
               .clip(CircleShape)
       )
       Text(
           text = stringResource(text),
           style = MaterialTheme.typography.h3,
           modifier = Modifier.paddingFromBaseline(
               top = 24.dp, bottom = 8.dp
           )
       )
   }
}

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun AlignYourBodyElementPreview() {
   MySootheTheme {
       AlignYourBodyElement(
           text = R.string.ab1_inversions,
           drawable = R.drawable.ab1_inversions,
           modifier = Modifier.padding(8.dp)
       )
   }
}

6. 「最愛的收藏」資訊卡 - Material Surface

下一個可組合項的實作方式與「調整身體狀態」元素的方式類似。設計如下 (包括紅線):

71fcfc487ef8c02a.png

f2f4fb696389ba4f.png

在這種情況下,會提供可組合項的完整大小。如您所見,文字也應該採用 H3 字體排版。

這個容器的背景顏色有多種,與整個螢幕的背景不同。此外,該容器也具有圓角。由於設計人員未指定顏色,我們可以假設顏色將由主題定義。針對這個容器,我們使用 Material 的 Surface 可組合項。

您可以根據自己的需求設定 Surface 的參數和修飾詞。在本範例中,途徑應加上圓角。這時可以使用 shape 參數。以使用 Material 主題中的值,取代上一步將圖片形狀設定為 Shape

其外觀如下:

import androidx.compose.foundation.layout.Row
import androidx.compose.material.Surface

@Composable
fun FavoriteCollectionCard(
   modifier: Modifier = Modifier
) {
   Surface(
       shape = MaterialTheme.shapes.small,
       modifier = modifier
   ) {
       Row {
           Image(
               painter = painterResource(R.drawable.fc2_nature_meditations),
               contentDescription = null
           )
           Text(
               text = stringResource(R.string.fc2_nature_meditations)
           )
       }
   }
}

我們來看看此實作的預覽:

5584e459f9838f8b.png

接著,套用您在上一個步驟中學到的經驗。設定圖片大小,並在容器中裁剪圖片大小。設定 Row 的寬度,並垂直對齊子項。建議您先自行實作這些變更,再查看解決方案程式碼!

程式碼現在大致如下所示:

import androidx.compose.foundation.layout.width

@Composable
fun FavoriteCollectionCard(
   modifier: Modifier = Modifier
) {
   Surface(
       shape = MaterialTheme.shapes.small,
       modifier = modifier
   ) {
       Row(
           verticalAlignment = Alignment.CenterVertically,
           modifier = Modifier.width(192.dp)
       ) {
           Image(
               painter = painterResource(R.drawable.fc2_nature_meditations),
               contentDescription = null,
               contentScale = ContentScale.Crop,
               modifier = Modifier.size(56.dp)
           )
           Text(
               text = stringResource(R.string.fc2_nature_meditations)
           )
       }
   }
}

預覽現在應如下所示:

e0afeb1658a6d82a.png

如要完成這個可組合項,請按照下列步驟操作:

  • 讓圖片和文字保持動態。並將其做為引數傳遞至可組合函式。
  • 更新文字以使用正確的字型樣式。
  • 更新圖片和文字之間的間距。

最終結果應如下所示:

@Composable
fun FavoriteCollectionCard(
   @DrawableRes drawable: Int,
   @StringRes text: Int,
   modifier: Modifier = Modifier
) {
   Surface(
       shape = MaterialTheme.shapes.small,
       modifier = modifier
   ) {
       Row(
           verticalAlignment = Alignment.CenterVertically,
           modifier = Modifier.width(192.dp)
       ) {
           Image(
               painter = painterResource(drawable),
               contentDescription = null,
               contentScale = ContentScale.Crop,
               modifier = Modifier.size(56.dp)
           )
           Text(
               text = stringResource(text),
               style = MaterialTheme.typography.h3,
               modifier = Modifier.padding(horizontal = 16.dp)
           )
       }
   }
}

//..

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun FavoriteCollectionCardPreview() {
   MySootheTheme {
       FavoriteCollectionCard(
           text = R.string.fc2_nature_meditations,
           drawable = R.drawable.fc2_nature_meditations,
           modifier = Modifier.padding(8.dp)
       )
   }
}

7. 「調整身體狀態」列 - 排列

您已建立畫面上顯示的基本可組合項,接著就可以開始建立畫面的不同部分。

從「調整身體狀態」可捲動列開始。

25089e1f3e5eab4e.gif

以下是這項元件的紅線設計:

9d943fabcb5ae632.png

請記得,一個格狀區塊代表 8dp。因此在本設計中,資料列的第一個項目之前和最後一個項目之後有 16dp 的空間。每個項目之間留有 8dp 的間距。

在 Compose 中,您可以使用 LazyRow 可組合項,實作這類可捲動的列。清單說明文件 包含關於 Lazy 清單的詳細資訊 (例如 LazyRowLazyColumn)。在本程式碼研究室中,請務必注意 LazyRow 只會轉譯在螢幕上顯示的元素,而非同時顯示所有元素,這有助於維持應用程式效能。

請先從這個 LazyRow 的基本實作著手:

import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items

@Composable
fun AlignYourBodyRow(
   modifier: Modifier = Modifier
) {
   LazyRow(
       modifier = modifier
   ) {
       items(alignYourBodyData) { item ->
           AlignYourBodyElement(item.drawable, item.text)
       }
   }
}

如您所見,LazyRow 的子項不是可組合項。請改用 Lazy 清單 DSL,以提供 itemitems 等方法,發出可組合項做為清單項目。針對提供的 alignYourBodyData 中的每個項目,您可以發出先前實作的 AlignYourBodyElement 可組合項。

請注意這將如何展示:

b88f30efe9067f53.png

但在紅線的設計中,依然缺少間距。如要實作這些項目,您必須瞭解安排

在上一個步驟中,您學到了對齊的對齊方式,可用於對齊交叉軸容器的子項。Column 的交叉軸是水平軸,Row 的時候交叉軸則是垂直軸。

不過,我們也可以決定如何將子項可組合項置於容器的主軸 (水平為 Row,垂直為 Column)。

針對 Row,您可以選擇下列排列方式:

c1e6c40e30136af2.gif

若是 Column

df69881d07b064d0.gif

除了這些排列方式之外,您也可以使用 Arrangement.spacedBy() 方法,為每個子項可組合項之間新增固定空間。

在這個範例中,如果您想 LazyRow 中每個項目之間留有 8dp 的間距,則必須使用 spacedBy 方法。

import androidx.compose.foundation.layout.Arrangement

@Composable
fun AlignYourBodyRow(
   modifier: Modifier = Modifier
) {
   LazyRow(
       horizontalArrangement = Arrangement.spacedBy(8.dp),
       modifier = modifier
   ) {
       items(alignYourBodyData) { item ->
           AlignYourBodyElement(item.drawable, item.text)
       }
   }
}

現在,設計的外觀如下所示:

c29a8ee73f218868.png

您也必須在 LazyRow 的側邊新增邊框間距。在這種情況下,即使加入簡單的邊框間距修飾詞,也不一定能發揮作用。請嘗試在 LazyRow 中加入邊框間距,看看其運作方式:

6b3f390040e2b7fd.gif

如您所見,在捲動畫面時,第一個和最後一個可見項目會在螢幕兩側遭到截斷。

為了保持相同的邊框間距,並確保在父項清單的邊界內捲動內容時不會截斷內容,所有清單都會提供名為 contentPadding 的參數。

@Composable
fun AlignYourBodyRow(
   modifier: Modifier = Modifier
) {
   LazyRow(
       horizontalArrangement = Arrangement.spacedBy(8.dp),
       contentPadding = PaddingValues(horizontal = 16.dp),
       modifier = modifier
   ) {
       items(alignYourBodyData) { item ->
           AlignYourBodyElement(item.drawable, item.text)
       }
   }
}

8. 「最愛的收藏」格線 - Lazy 格線

下一個要導入的部分是畫面中的「最愛的收藏」部分。這個可組合項需要格狀版面,而非單列版面:

4378867d758590ae.gif

您可以採用與上一個部分類似的方式實作這個部分,做法是建立 LazyRow,並讓每個項目保留包含兩個 FavoriteCollectionCard 例項的 Column。不過,在這個步驟中,您將使用 LazyHorizontalGrid,這種做法可以更妥善地將項目對應至格狀版面元素。

請先實作包含兩個固定列的簡單格狀版面:

import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.items

@Composable
fun FavoriteCollectionsGrid(
   modifier: Modifier = Modifier
) {
   LazyHorizontalGrid(
       rows = GridCells.Fixed(2),
       modifier = modifier
   ) {
       items(favoriteCollectionsData) { item ->
           FavoriteCollectionCard(item.drawable, item.text)
       }
   }
}

如您所見,只要將上一個步驟中的 LazyRow 替換成 LazyHorizontalGrid 即可。

不過,目前無法提供正確的搜尋結果:

e51beb5c00457902.png

格狀空間會佔據父項空間,也就是說,「最愛的收藏」資訊卡已垂直延伸太多。調整可組合項,讓格線儲存格之間有適當的大小和間距。

測試看起來會像這樣:

@Composable
fun FavoriteCollectionsGrid(
   modifier: Modifier = Modifier
) {
   LazyHorizontalGrid(
       rows = GridCells.Fixed(2),
       contentPadding = PaddingValues(horizontal = 16.dp),
       horizontalArrangement = Arrangement.spacedBy(8.dp),
       verticalArrangement = Arrangement.spacedBy(8.dp),
       modifier = modifier.height(120.dp)
   ) {
       items(favoriteCollectionsData) { item ->
           FavoriteCollectionCard(
               drawable = item.drawable,
               text = item.text,
               modifier = Modifier.height(56.dp)
           )
       }
   }
}

9. 首頁區段 - Slot API

MySoothe 主畫面有多個區段遵循相同的模式。每個影片都有標題,但部分內容可能會因區塊而異。我們想要實作的設計如下:

2c0bc456d50bb078.png

如您所見,每個部分都有標題版位。標題包含一些相關的間距和樣式資訊。視區段而定,版位可能會以不同內容動態填入。

如要實作這個彈性區段容器,請使用「Slot API」。開始實作前,請先參閱說明文件頁面的「以版位為基礎的版面配置」一節。這將有助於瞭解什麼是以版位為基礎的版面配置,以及如何使用 Slot API 建立這類版面配置。

調整 HomeSection 可組合項以接收標題和版位內容。您也應調整相關聯的預覽,以呼叫具有「調整身體狀態」標題和內容的 HomeSection

@Composable
fun HomeSection(
   @StringRes title: Int,
   modifier: Modifier = Modifier,
   content: @Composable () -> Unit
) {
   Column(modifier) {
       Text(stringResource(title))
       content()
   }
}

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun HomeSectionPreview() {
   MySootheTheme {
       HomeSection(R.string.align_your_body) {
           AlignYourBodyRow()
       }
   }
}

您可以將 content 參數用於可組合項的版位。這樣一來,使用 HomeSection 可組合項時,就能使用結尾的 lambda 填滿內容版位。當可組合項提供多個可填入的版位時,您可在大型可組合容器中為其取可代表其函式意義的名稱。舉例來說,Material 的 TopAppBar 提供 titlenavigationIconactions 的版位。

以下是採用上述實作方式後的區段外觀:

d824b60e650deeb.png

Text 可組合項需要更多資訊,才能與設計對齊。更新程式碼,以便:

  • 全部以大寫顯示 (提示:您可以使用 Stringuppercase() 方法)。
  • 使用了 H2 字體排版。
  • 具有符合紅線設計的邊框間距。

最終解決方案應如下所示:

import java.util.*

@Composable
fun HomeSection(
   @StringRes title: Int,
   modifier: Modifier = Modifier,
   content: @Composable () -> Unit
) {
   Column(modifier) {
       Text(
           text = stringResource(title).uppercase(Locale.getDefault()),
           style = MaterialTheme.typography.h2,
           modifier = Modifier
               .paddingFromBaseline(top = 40.dp, bottom = 8.dp)
               .padding(horizontal = 16.dp)
       )
       content()
   }
}

10. 主畫面 - 捲動頁面

現在,您已分別建立了不同的建構區塊,可以將其合併為全螢幕實作。

以下是您嘗試實作的設計:

a535e10437e9df31.png

我們只將搜尋列和以下兩個部分放在一起。您必須加入一些間距,才能確保一切適合目前的設計。我們從未使用過的可組合項為 Spacer,這有助於我們在 Column 中納入額外的空間。如果您改為設定 Column 的邊框間距,系統會提供先前在「最愛的收藏」集合標題中相同的截斷行為。

@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
   Column(modifier) {
       Spacer(Modifier.height(16.dp))
       SearchBar(Modifier.padding(horizontal = 16.dp))
       HomeSection(title = R.string.align_your_body) {
           AlignYourBodyRow()
       }
       HomeSection(title = R.string.favorite_collections) {
           FavoriteCollectionsGrid()
       }
       Spacer(Modifier.height(16.dp))
   }
}

雖然這個設計適合大多數裝置尺寸,但在裝置螢幕空間不足 (例如在橫向模式下) 時,應該要能垂直捲動畫面。您必須新增捲動行為。

如先前所述,LazyRowLazyHorizontalGrid 等 Lazy 版面配置會自動新增捲動行為。不過,您不一定需要 Lazy 版面配置。一般來說,當清單含有許多元素,或需要載入龐大的資料集時,您會使用 Lazy 版面配置。因此,如果一次發送所有項目,就會導致效能降低,並減慢應用程式的執行速度。如果清單僅包含少量元素,您可以改為使用簡單的 ColumnRow,然後手動新增捲動行為。方法是使用 verticalScrollhorizontalScroll 修飾詞。需要 ScrollState,其中包含捲動目前的狀態,以便從外部修改捲動狀態。在這種情況下,您無需修改捲動狀態,只需使用 rememberScrollState 建立持續的 ScrollState 執行個體即可。

最終結果應如下所示:

import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll

@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
   Column(
       modifier
           .verticalScroll(rememberScrollState())
           .padding(vertical = 16.dp)
   ) {
       Spacer(Modifier.height(16.dp))
       SearchBar(Modifier.padding(horizontal = 16.dp))
       HomeSection(title = R.string.align_your_body) {
           AlignYourBodyRow()
       }
       HomeSection(title = R.string.favorite_collections) {
           FavoriteCollectionsGrid()
       }
       Spacer(Modifier.height(16.dp))
   }
}

如要驗證可組合項的捲動行為,請限制預覽的高度,並在互動式預覽中執行:

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2, heightDp = 180)
@Composable
fun ScreenContentPreview() {
   MySootheTheme { HomeScreen() }
}

11. 底部導覽 - Material

現在您已實作畫面內容,現在可以新增視窗裝飾了。以 MySoothe 為例,底部導覽列可讓使用者切換不同畫面。

首先,請實作這個底部導覽可組合項,然後將其納入應用程式。

讓我們來看看設計:

2f42ad2b882885f8.png

幸好,您不需要自行實作整個可組合項。您可以使用屬於 Compose Material 程式庫的 BottomNavigation 可組合項。在 BottomNavigation 可組合項中,您可以新增一或多個 BottomNavigationItem 元素,隨後 Material 程式庫會自動套用樣式。

請先從底部導覽列的基本實作著手:

import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Spa

@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
   BottomNavigation(modifier) {
       BottomNavigationItem(
           icon = {
               Icon(
                   imageVector = Icons.Default.Spa,
                   contentDescription = null
               )
           },
           label = {
               Text(stringResource(R.string.bottom_navigation_home))
           },
           selected = true,
           onClick = {}
       )
       BottomNavigationItem(
           icon = {
               Icon(
                   imageVector = Icons.Default.AccountCircle,
                   contentDescription = null
               )
           },
           label = {
               Text(stringResource(R.string.bottom_navigation_profile))
           },
           selected = false,
           onClick = {}
       )
   }
}

此基本導入範例將如下所示:

5bdb7729d75e1a72.png

有一些樣式應該調整。首先,您可以透過設定底部導覽的 backgroundColor 參數,更新底部導覽列的背景顏色。您可以使用質感設計主題中的背景顏色。設定背景顏色後,圖示和文字的顏色就會自動調整至主題的 onBackground 顏色。最終的解決方案應如下所示:

@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
   BottomNavigation(
       backgroundColor = MaterialTheme.colors.background,
       modifier = modifier
   ) {
       BottomNavigationItem(
           icon = {
               Icon(
                   imageVector = Icons.Default.Spa,
                   contentDescription = null
               )
           },
           label = {
               Text(stringResource(R.string.bottom_navigation_home))
           },
           selected = true,
           onClick = {}
       )
       BottomNavigationItem(
           icon = {
               Icon(
                   imageVector = Icons.Default.AccountCircle,
                   contentDescription = null
               )
           },
           label = {
               Text(stringResource(R.string.bottom_navigation_profile))
           },
           selected = false,
           onClick = {}
       )
   }
}

12. MySoothe 應用程式 - Scaffold

最後一步,建立全螢幕,包括底部導覽。使用 Material 的 Scaffold 可組合項。Scaffold 針對導入 Material 設計的應用程式,提供頂層可設定的可組合項。其中包含各種 Material 概念的版位,其中一個版位為底部列。在底部列中,您可以放置在上一個步驟中建立的底部導覽可組合項。

實作 MySootheApp 可組合項。這是應用程式的頂層可組合項,因此您應該:

  • 套用 MySootheTheme 質感設計主題。
  • 加入 Scaffold
  • 將底部列設為 SootheBottomNavigation 可組合項。
  • 將內容設為 HomeScreen 可組合項。

最終結果應為:

import androidx.compose.material.Scaffold

@Composable
fun MySootheApp() {
   MySootheTheme {
       Scaffold(
           bottomBar = { SootheBottomNavigation() }
       ) { padding ->
           HomeScreen(Modifier.padding(padding))
       }
   }
}

您的實作程序已經完成!如要檢查您的版本是否已以完美像素實作,請下載下方圖片,並與自己的預覽實作進行比較。

24ff9efa75f22198.png

13. 恭喜

恭喜!您已成功完成本程式碼研究室,並進一步瞭解 Compose 中的版面配置。透過實作真實世界的設計,您已瞭解修飾詞、對齊、排列、Lazy 版面配置、Slot API、捲動和 Material 元件。

請參閱 Compose 課程中的其他程式碼研究室。並查看程式碼範例

說明文件

如需有關這些主題的更多資訊和指南,請參閱以下說明文件: