使用 Jetpack Compose 進行主題設定

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

1. 簡介

在本程式碼研究室中,您將瞭解如何透過使用 Jetpack Compose 的主題設定 API 設定應用程式樣式。我們會說明如何自訂顏色、形狀和字型排版,以便在整個應用程式中一致使用,並支援淺色和深色主題等多個主題。

學習目標

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

  • Material Design 的入門介紹,以及如何根據品牌需求進行自訂
  • Compose 質感設計系統的實作方式
  • 如何在應用程式中定義及使用顏色、字體排版和形狀
  • 如何設定元件樣式
  • 如何支援淺色和深色主題

建構目標

在本程式碼研究室中,我們會為新聞閱讀應用程式設定樣式。我們先從未設定樣式的應用程式開始做起,然後再運用所學知識來打造應用程式主題,並支援深色主題。

新聞閱讀應用程式 Jetnews 套用樣式前的圖片。

新聞閱讀應用程式 Jetnews 套用樣式後的圖片。

這個圖片將顯示 Jetnews,一個以深色主題設計的新聞閱讀應用程式。

變更前:未設定樣式的應用程式

「變更後:樣式化應用程式」

「變更後:深色主題」

必要條件

2. 開始設定

在這個步驟中,您將下載適用的程式碼,其中包含我們要設定樣式的簡易新聞閱讀器應用程式。

需求條件

下載程式碼

如果您已安裝 Git,只要執行下列指令即可。如要檢查 Git 是否已安裝完成,請在終端機或指令列中輸入 git --version,並確認該指令可正確執行。

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

如果您沒有 Git,可以點選下方按鈕,下載這個程式碼研究室的所有程式碼:

在 Android Studio 中開啟專案,依序選取「File」>「Import Project」,然後前往 ThemingCodelab 目錄。

這項專案含有三個主要套件:

  • com.codelab.theming.data 包含模型類別和範例資料。在本程式碼研究室中,這個套件不需要編輯。
  • com.codelab.theming.ui.start 這是此程式碼研究室的起點,您應該在這個套件中進行本程式碼研究室中的所有變更。
  • com.codelab.theming.ui.finish 這是程式碼研究室的結束狀態,供您參考。

建立應用程式並加以執行

應用程式有 2 個執行設定,代表程式碼研究室的開始和結束狀態。選取設定並按下執行按鈕,即可將程式碼部署至裝置或模擬器。

a43ae3c4fa75836e.png

這個應用程式也具有 Compose 版面配置預覽。在 start/finish 其中一個套件中瀏覽至 Home.kt,並開啟設計檢視畫面,其中會顯示幾個預覽畫面,方便您在 UI 程式碼中快速疊代:

758a285ad8a6cd51.png

3. Material Design 主題設定

Jetpack Compose 提供 Material Design 這項產品,這是用於建立數位介面的全方位設計系統。Material Design 元件 (按鈕、資訊卡、切換按鈕等) 是以 Material Design 主題設定為基礎建構而成,能夠以系統化的方式自訂 Material Design,更準確地反映產品品牌。質感設計主題由顏色字體排版形狀屬性構成。自訂這些元素會自動反映在您建構應用程式的元件中。

瞭解 Material Design 主題設定設定有助於瞭解如何為 Jetpack Compose 應用程式設定主題,以下簡短說明相關概念。如果您已熟悉質感設計主題設定,可以選擇略過。

顏色

Material Design 定義了一些按照語意命名的顏色,可用於整個應用程式:

62ccfe5761fd9eda.png

主要顏色為主要品牌顏色,次要顏色則用於提供重點色彩。您可以為對比的區域提供顏色更深/更淺的變化版本。背景和表面顏色可用於元件所在的容器,這些元件通常位於應用程式中的「表面」。質感設計也定義了「上方」顏色,這些顏色用於任一已命名色彩上方的內容,例如「表面」彩色容器中的文字顏色應為在「表面上方」的顏色。質感元件會設為使用這些主題顏色,例如,懸浮動作按鈕預設為 secondary資訊卡預設為 surface

定義已命名顏色後,應用程式即可提供替代調色盤,例如淺色和深色主題:

1a9b78141ddfa87b.png

我們也鼓勵您定義小型的調色盤,並在整個應用程式中採用一致的調色盤。Material 色彩工具可協助您挑選顏色並建立調色盤,甚至確保方便存取組合。

字體排版

同樣地,質感設計會定義許多依語意命名的樣式類型:

1d44de3ff2f7fd1c.png

儘管可能無法依主題採用不同的類型樣式,但使用類型縮放調整功能可以提高在應用程式中的一致性。提供自己的字型和其他類型自訂設定,其會反映在應用程式中使用的質感元件中。舉例來說,App Bars (應用程式列) 預設使用 h6 樣式,Buttons (按鈕) 使用的也是 button。質感類型調整產生器工具可協助您建構類型大小。

形狀

Material Design 支援使用形狀有條理地呈現您的品牌。可定義 3 種類別:小型、中型和大型元件;每種類別都可以定義要使用的形狀,並自訂邊角樣式 (直角或圓角) 和大小。

886b811cc9cad18e.png

自訂形狀主題會呈現在許多元件上,舉例來說,根據預設,按鈕文字欄位會使用小型形狀主題,資訊卡對話方塊使用中型形狀主題,試算表則使用大型形狀主題。元件的形狀主題的完整對應請見這裡。Material 形狀自訂工具可協助您產生形狀主題。

基準

質感設計會預設為「基準」主題,也就是紫色色彩配置、Roboto 類型比例,以及上圖所示的略微圓角形狀。如未指定或自訂主題,元件將使用基準主題。

4. 定義主題

MaterialTheme

在 Jetpack Compose 中主題設定實作的核心元素是 MaterialTheme 可組合項。將這個可組合項放入可組合階層中,您就能指定自訂元件中所有元件的色彩、類型和形狀。以下是在程式庫中定義這個可組合項的方式:

@Composable
fun MaterialTheme(
    colors: Colors,
    typography: Typography,
    shapes: Shapes,
    content: @Composable () -> Unit
) { ...

您日後可以使用 MaterialTheme object 擷取傳送至這個可組合項的參數,這些參數會顯示 colorstypographyshapes 屬性。我們稍後會進一步說明相關細節。

開啟 Home.kt 並找出 Home 可組合函式,這是應用程式的主要進入點。請注意,在宣告 MaterialTheme 時,我們不會指定任何參數,因此會接收預設的「基準」樣式:

@Composable
fun Home() {
  ...
  MaterialTheme {
    Scaffold(...

讓我們建立顏色、類型和形狀參數,以便為應用程式實作主題。

新建主題

如要集中管理樣式,建議您自行建立包裝及設定 MaterialTheme 的可組合項。如此一來,您就能透過單一介面指定主題自訂項目,而且在多個位置 (例如多個螢幕或 @Preview) 中都能輕鬆重複使用。您可以視需要建立多個主題可組合項,例如針對應用程式的不同部分支援不同樣式。

com.codelab.theming.ui.start.theme 套件中,建立名為 Theme.kt 的新檔案。新增名為 JetnewsTheme 的可組合函式,接受其他可組合項做為內容,並納入 MaterialTheme

@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
  MaterialTheme(content = content)
}

現在切換回 Home.kt 並使用 JetnewsTheme 取代 MaterialTheme (然後匯入):

-  MaterialTheme {
+  JetnewsTheme {
    ...

這個畫面的 @Preview 中暫時不會有任何變更。更新 PostItemPreviewFeaturedPostPreview,將其內容納入新的 JetnewsTheme 可組合項,讓預覽畫面使用新的主題:

@Preview("Featured Post")
@Composable
private fun FeaturedPostPreview() {
  val post = remember { PostRepo.getFeaturedPost() }
+ JetnewsTheme {
    FeaturedPost(post = post)
+ }
}

顏色

以下是我們要在應用程式中實作的調色盤 (目前僅有淺色調色盤,近期將支援深色主題):

b2635ed3ec4bfc8f.png

Compose 中的顏色是使用 Color 類別定義。有多個建構函式可讓您將顏色指定為 ULong 或由不同的顏色管道指定。

theme 套件中建立新檔案 Color.kt。請在這個檔案中新增下列顏色做為頂層公開屬性:

val Red700 = Color(0xffdd0d3c)
val Red800 = Color(0xffd00036)
val Red900 = Color(0xffc20029)

我們定義了應用程式顏色後,請將這些顏色合併到 MaterialTheme 所需的 Colors 物件中,並為質感設計的已命名顏色指派特定顏色。切換回 Theme.kt,並新增以下內容:

private val LightColors = lightColors(
    primary = Red700,
    primaryVariant = Red900,
    onPrimary = Color.White,
    secondary = Red700,
    secondaryVariant = Red900,
    onSecondary = Color.White,
    error = Red800
)

此處使用 lightColors 函式建構 Colors,提供合理的預設值,因此我們不必指定組成質感調色盤的所有顏色。例如請注意,我們並未指定 background 顏色或許多「上方」顏色,此時會使用預設設定。

現在,讓我們在應用程式中使用這些顏色。更新 JetnewsTheme 可組合項就能使用新的 Colors

@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
  MaterialTheme(
+   colors = LightColors,
    content = content
  )
}

開啟「Home.kt」並重新整理預覽畫面。請注意,新的色彩配置會反映在 TopAppBar 等元件中。

字體排版

我們想要在應用程式中實作的輸入比例如下:

54c420f78529b77d.png

在 Compose 中,我們可以定義 TextStyle 物件,藉此定義設定部分文字樣式所需的資訊。屬性範例:

data class TextStyle(
    val color: Color = Color.Unset,
    val fontSize: TextUnit = TextUnit.Inherit,
    val fontWeight: FontWeight? = null,
    val fontStyle: FontStyle? = null,
    val fontFamily: FontFamily? = null,
    val letterSpacing: TextUnit = TextUnit.Inherit,
    val background: Color = Color.Unset,
    val textAlign: TextAlign? = null,
    val textDirection: TextDirection? = null,
    val lineHeight: TextUnit = TextUnit.Inherit,
    ...
)

我們期望的類型調整功能使用 Montserrat 來處理標題,使用 Domine 來處理內文。相關字型檔案已新增至專案的 res/fonts 資料夾。

theme 套件中建立新檔案 Typography.kt。首先,定義 FontFamily (結合每個 Font 的不同權重):

private val Montserrat = FontFamily(
    Font(R.font.montserrat_regular),
    Font(R.font.montserrat_medium, FontWeight.W500),
    Font(R.font.montserrat_semibold, FontWeight.W600)
)

private val Domine = FontFamily(
    Font(R.font.domine_regular),
    Font(R.font.domine_bold, FontWeight.Bold)
)

現在,建立 MaterialTheme 可接受的 Typography 物件,並在比例中指定每個語意樣式的 TextStyle

val JetnewsTypography = Typography(
    h4 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W600,
        fontSize = 30.sp
    ),
    h5 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W600,
        fontSize = 24.sp
    ),
    h6 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W600,
        fontSize = 20.sp
    ),
    subtitle1 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    ),
    subtitle2 = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W500,
        fontSize = 14.sp
    ),
    body1 = TextStyle(
        fontFamily = Domine,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp
    ),
    body2 = TextStyle(
        fontFamily = Montserrat,
        fontSize = 14.sp
    ),
    button = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W500,
        fontSize = 14.sp
    ),
    caption = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.Normal,
        fontSize = 12.sp
    ),
    overline = TextStyle(
        fontFamily = Montserrat,
        fontWeight = FontWeight.W500,
        fontSize = 12.sp
    )
)

開啟 Theme.kt 並更新 JetnewsTheme 可組合項,以使用新的 Typography

@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
  MaterialTheme(
    colors = LightColors,
+   typography = JetnewsTypography,
    content = content
  )
}

開啟 Home.kt 並重新整理預覽畫面,查看新的字體排版效果。

形狀

我們想利用形狀在應用程式中呈現品牌。我們想在多項元素上使用直角形狀,如下所示:

9b60c78a78c61570.png

Compose 提供 RoundedCornerShapeCutCornerShape 類別,可用來定義形狀主題。

theme 套件中建立新檔案 Shape.kt,並新增下列指令:

val JetnewsShapes = Shapes(
    small = CutCornerShape(topStart = 8.dp),
    medium = CutCornerShape(topStart = 24.dp),
    large = RoundedCornerShape(8.dp)
)

開啟 Theme.kt 並更新 JetnewsTheme 可組合項,就能使用以下 Shapes

@Composable
fun JetnewsTheme(content: @Composable () -> Unit) {
  MaterialTheme(
    colors = LightColors,
    typography = JetnewsTypography,
+   shapes = JetnewsShapes,
    content = content
  )
}

開啟 Home.kt 並重新整理預覽畫面,查看顯示精選貼文的 Card 如何反映新套用的形狀主題。

深色主題

在應用程式中支援深色主題,不僅有助於改善應用程式在使用者裝置上的整合狀態 (從 Android 10 以上版本開始提供通用深色主題切換功能),還能降低耗電量和滿足無障礙需求。質感設計提供了設計指南,說明如何建立深色主題。以下是我們想為深色主題實作的替代調色盤:

21768b33f0ccda5f.png

開啟 Color.kt 並新增下列顏色:

val Red200 = Color(0xfff297a2)
val Red300 = Color(0xffea6d7e)

現在,請開啟 Theme.kt 並新增:

private val DarkColors = darkColors(
    primary = Red300,
    primaryVariant = Red700,
    onPrimary = Color.Black,
    secondary = Red300,
    onSecondary = Color.Black,
    error = Red200
)

現在更新 JetnewsTheme

@Composable
fun JetnewsTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
  content: @Composable () -> Unit
) {
  MaterialTheme(
+   colors = if (darkTheme) DarkColors else LightColors,
    typography = JetnewsTypography,
    shapes = JetnewsShapes,
    content = content
  )
}

我們在這裡新增了新的參數以使用深色主題,並預設為查詢裝置以尋找 全域設定。這提供了良好的預設值,但如果想讓特定畫面永遠/永不顯示為深色,或者設定深色主題為 @Preview,還是可以輕鬆覆寫。

開啟 Home.kt 並為 FeaturedPost 可組合項目建立新預覽畫面,並以深色主題顯示:

@Preview("Featured Post • Dark")
@Composable
private fun FeaturedPostDarkPreview() {
    val post = remember { PostRepo.getFeaturedPost() }
    JetnewsTheme(darkTheme = true) {
        FeaturedPost(post = post)
    }
}

重新整理預覽窗格,即可查看深色主題預覽。

84f93b209ce4fd46.png

5. 使用色彩

在上一個步驟中,我們瞭解如何自行建立主題,為應用程式設定顏色、類型樣式和形狀。所有質感元件都能直接使用這些自訂項目。舉例來說,FloatingActionButton 可組合項預設會使用主題中的 secondary 顏色,但您可以為這個參數指定不同的值,設定不同顏色:

@Composable
fun FloatingActionButton(
  backgroundColor: Color = MaterialTheme.colors.secondary,
  ...
) {

您不一定會使用預設設定。本節將說明如何在應用程式中使用色彩。

原始色彩

如先前所述,Compose 提供 Color 類別。您可以在本機建立這些顏色,並將其存放於 object 等元件:

Surface(color = Color.LightGray) {
  Text(
    text = "Hard coded colors don't respond to theme changes :(",
    textColor = Color(0xffff00ff)
  )
}

Color 提供多種實用的方法,例如 copy 可讓您以不同 Alpha/紅色/綠色/藍色值建立新的顏色。

主題顏色

更靈活的做法是從主題中擷取顏色:

Surface(color = MaterialTheme.colors.primary)

我們使用 MaterialTheme object,其中 colors 屬性會傳回 MaterialTheme 可組合項中設定的 Colors。也就是說,只要為我們的主題提供不同的顏色組合,不需要變更應用程式程式碼,就能支援不同的外觀和風格樣式。舉例來說,我們的 AppBar 使用 primary 顏色,且螢幕背景為 surface;變更佈景主題色彩會反映在這些可組合項中:

b0b0ca02b52453a7.png

253ab041d7ea904e.png

由於主題中的每種顏色都是 Color 例項,我們也可以使用 copy 方法輕鬆「衍生」顏色:

val derivedColor = MaterialTheme.colors.onSurface.copy(alpha = 0.1f)

我們要建立 onSurface 顏色的副本,但不透明度為 10%。這種做法可確保色彩在不同主題下正常運作,而不是以硬式編碼的方式寫入靜態色彩。

表面和內容顏色

許多元件都接受一組顏色和「內容顏色」:

Surface(
  color: Color = MaterialTheme.colors.surface,
  contentColor: Color = contentColorFor(color),
  ...

TopAppBar(
  backgroundColor: Color = MaterialTheme.colors.primarySurface,
  contentColor: Color = contentColorFor(backgroundColor),
  ...

這不僅可讓您設定可組合項的色彩,還能為「內容」(例如其中的可組合項) 提供預設顏色。許多可組合項會預設使用這個內容顏色,例如 Text 顏色或 Icon 色調。contentColorFor 方法會擷取任何主題色彩的適當「上方」顏色。例如,如果設定了 primary 背景,此方法會傳回 onPrimary 做為內容顏色。如果您設定了非主題背景顏色,請務必自行提供合理的內容顏色。

Surface(color = MaterialTheme.colors.primary) {
  Text(...) // default text color is 'onPrimary'
}
Surface(color = MaterialTheme.colors.error) {
  Icon(...) // default tint is 'onError'
}

您可以使用 LocalContentColor CompositionLocal 擷取與目前背景形成對比的顏色:

BottomNavigationItem(
  unselectedContentColor = LocalContentColor.current ...

設定任何元素的顏色時,建議您使用 Surface,這樣做可設定合適的內容顏色 CompositionLocal 值。請注意直接呼叫 Modifier.background 並不會設定適當的內容顏色。

-Row(Modifier.background(MaterialTheme.colors.primary)) {
+Surface(color = MaterialTheme.colors.primary) {
+  Row(
...

目前,Header 元件一律具有 Color.LightGray 背景。在淺色主題中看起來很正常,但與深色主題中的背景對比度高。他們也未指定特定的文字顏色,因此沿用目前可能不會與背景形成對比的顏色作為內容顏色:

7329ac6ead5097eb.png

讓我們一起解決這個問題。在 Home.ktHeader 可組合項中,移除指定硬式編碼顏色的 background 修飾符。請改為將 Text 包裝在包含主題衍生顏色的 Surface 中,並指定內容的顏色應設為 primary

+ Surface(
+   color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
+   contentColor = MaterialTheme.colors.primary,
+   modifier = modifier
+ ) {
  Text(
    text = text,
    modifier = Modifier
      .fillMaxWidth()
-     .background(Color.LightGray)
      .padding(horizontal = 16.dp, vertical = 8.dp)
  )
+ }

內容 Alpha 值

我們通常會想強調或淡化內容,藉此傳達重要資訊並提供視覺層級。Material Design 建議採用不同程度的不透明度,以傳達不同的重要性等級。

Jetpack Compose 會透過 LocalContentAlpha 執行這項工作。您可以為這個 CompositionLocal 提供值,從而為階層指定內容 Alpha 值。子項可組合項可使用這個值,例如:TextIcon 預設會使用調整為使用 LocalContentAlpha 後的 LocalContentColor 組合。質感設計會指定某些標準 Alpha 值 (highmediumdisabled),此模型是透過 ContentAlpha 物件所建立。請注意,MaterialTheme 預設 LocalContentAlphaContentAlpha.high

// By default, both Icon & Text use the combination of LocalContentColor &
// LocalContentAlpha. De-emphasize content by setting a different content alpha
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
    Text(...)
}
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Icon(...)
    Text(...)
}

這樣一來,就能輕鬆地透過一致的方式傳達元件的重要性。

我們會使用內容 Alpha 值來釐清精選貼文的資訊階層。在 Home.kt 中的 PostMetadata 可組合項中,讓中繼資料 medium 強調:

+ CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
  Text(
    text = text,
    modifier = modifier
  )
+ }

103ff62c71744935.png

深色主題

如先前所述,如要在 Compose 中實作深色主題,只需透過該主題提供不同的顏色組合並查詢顏色即可。幾個例外狀況如下:

您可以檢查是否在淺色主題中執行:

val isLightTheme = MaterialTheme.colors.isLight

這個值是由 lightColors (淺色) / darkColors (深色) 建構工具函式設定。

在質感設計中,深色主題中高度較高的表面將取得高度重疊 (背景會變亮)。使用深色調色盤時,系統會自動實作以下程式碼:

Surface(
  elevation = 2.dp,
  color = MaterialTheme.colors.surface, // color will be adjusted for elevation
  ...

我們在應用程式中使用的 TopAppBarCard 元件中都能看到這個自動行為;這些工具預設的高度為 4dp 和 1dp,因此使用深色主題時,背景會自動調亮,以更有效地傳達此高度:

cb8c617b8c151820.png

Material Design 建議避免深色主題中出現大量明亮的色彩。常見模式為在淺色主題中加上容器 primary 顏色,在深色主題中加上 surface 顏色。根據預設,許多元件都會採用這項策略,例如 App BarBottom Navigation。為了方便實作,Colors 提供 primarySurface 顏色,提供此行為及這些元件的預設使用選項。

應用程式目前會將 App Bar 設為 primary 顏色,但我們可將其改為 primarySurface,或直接移除這項參數 (採用預設值),這樣就能遵循這項原則。在 AppBar 可組合項中,變更 TopAppBarbackgroundColor 參數:

@Composable
private fun AppBar() {
  TopAppBar(
    ...
-   backgroundColor = MaterialTheme.colors.primary
+   backgroundColor = MaterialTheme.colors.primarySurface
  )
}

6. 處理文字

處理文字時,我們會使用 Text 可組合項以顯示文字、TextFieldOutlinedTextField 以輸入文字和 TextStyle,為文字套用單一樣式。我們可以使用 AnnotatedString 將多個樣式套用到文字。

我們發現有了顏色,顯示文字的質感元件會採取自訂主題字體排版:

Button(...) {
  Text("This text will use MaterialTheme.typography.button style by default")
}

實現此做法會比使用有顏色的預設參數略為複雜一些。這是因為元件通常不會自行顯示文字,而是提供 Slot API,可讓您傳入 Text 可組合項。那麼,元件該如何設定主題字型樣式?基本上,他們會使用 ProvideTextStyle 可組合項 (本身使用 CompositionLocal) 來設定「目前」的 TextStyle。如未提供具體的 textStyle 參數,Text 可組合項會預設查詢這個「目前」樣式。

例如,從 Compose 的 ButtonText 類別:

@Composable
fun Button(
    // many other parameters
    content: @Composable RowScope.() -> Unit
) {
  ...
  ProvideTextStyle(MaterialTheme.typography.button) { //set the "current" text style
    ...
    content()
  }
}

@Composable
fun Text(
    // many, many parameters
    style: TextStyle = LocalTextStyle.current // get the value set by ProvideTextStyle
) { ...

主題文字樣式

和使用顏色一樣,建議您從目前主題擷取 TextStyle,鼓勵使用一致的小型樣式組合,讓樣式更易於維護。MaterialTheme.typography 會擷取 MaterialTheme 可組合項中的 Typography 例項組合,方便您使用您定義的樣式:

Text(
  style = MaterialTheme.typography.subtitle2
)

如要自訂 TextStyle,您可以將其 copy 並覆寫屬性 (僅為 data class) 或 Text 可組合項接受多個重疊於任一 TextStyle 頂端的樣式參數:

Text(
  text = "Hello World",
  style = MaterialTheme.typography.body1.copy(
    background = MaterialTheme.colors.secondary
  )
)
Text(
  text = "Hello World",
  style = MaterialTheme.typography.subtitle2,
  fontSize = 22.sp // explicit size overrides the size in the style
)

應用程式中的許多地方都會自動套用主題 TextStyle,例如 TopAppBar 會將 title 的樣式設為 h6ListItem 則會將主要和次要文字的樣式分別設為 subtitle1body2

讓我們將主題字體樣式設定套用至應用程式的其他部分。設定 Header 以使用 subtitle2FeaturedPost 中的文字,以便使用 h6 做為標題,並將 body2 用做作者和中繼資料:

@Composable
fun Header(...) {
  ...
  Text(
    text = text,
+   style = MaterialTheme.typography.subtitle2

45dbf11d6c1013a0.png

多種樣式

如果需要為部分文字套用多種樣式,可以使用 AnnotatedString 類別來套用標記,並在文字範圍中加上 SpanStyle。您可以動態新增這些變數,或使用 DSL 語法建立內容:

val text = buildAnnotatedString {
  append("This is some unstyled text\n")
  withStyle(SpanStyle(color = Color.Red)) {
    append("Red text\n")
  }
  withStyle(SpanStyle(fontSize = 24.sp)) {
    append("Large text")
  }
}

讓我們為描述應用程式中每則貼文的標記設定樣式。這些標記目前使用與其餘中繼資料相同的文字樣式;我們將使用 overline 文字樣式和背景顏色加以區分。在 PostMetadata 可組合項中:

+ val tagStyle = MaterialTheme.typography.overline.toSpanStyle().copy(
+   background = MaterialTheme.colors.primary.copy(alpha = 0.1f)
+ )
post.tags.forEachIndexed { index, tag ->
  ...
+ withStyle(tagStyle) {
    append(" ${tag.toUpperCase()} ")
+ }
}

3f504aaa0a94599a.png

7. 使用形狀

就像顏色和字型一樣,設定形狀主題也會反映在質感元件中,例如 Button 會挑選小型元件的形狀集:

@Composable
fun Button( ...
  shape: Shape = MaterialTheme.shapes.small
) {

和色彩一樣,Material 元件會使用預設參數,因此您可以透過簡單直接的方式檢查元件會使用的形狀類別,或是提供替代選項。如需元件與形狀類別的完整對應關係,請參閱說明文件

請注意,部分元件會配合情境使用修改過的主題形狀。舉例來說,TextField 預設使用小型形狀主題,但會在底部角落套用零邊角:

@Composable
fun FilledTextField(
  // other parameters
  shape: Shape = MaterialTheme.shapes.small.copy(
    bottomStart = ZeroCornerSize, // overrides small theme style
    bottomEnd = ZeroCornerSize // overrides small theme style
  )
) {

1f5fa6cf1355e7a6.png

主題形狀

當然,在使用可組合項或接受 SurfaceModifier.clipModifier.backgroundModifier.border 等的 Modifier 建立自己的元件時,您可以自行使用形狀。

@Composable
fun UserProfile(
  ...
  shape: Shape = MaterialTheme.shapes.medium
) {
  Surface(shape = shape) {
    ...
  }
}

讓我們在 PostItem 中顯示的圖片中新增形狀主題設定;我們會套用主題的 small 形狀,並以 clip Modifier 剪下左上角:

@Composable
fun PostItem(...) {
  ...
  Image(
    painter = painterResource(post.imageThumbId),
+   modifier = Modifier.clip(shape = MaterialTheme.shapes.small)
  )

2f989c7c1b8d9e63.png

8. 元件「樣式」

Compose 對於擷取元件的樣式 (例如 Android View 樣式或 css 樣式) 並未提供明確的方法。由於所有 Compose 元件都是以 Kotlin 編寫而成,因此還有其他方法可以達成相同的目標。請改為自行建立自訂元件程式庫,以便在應用程式中全面使用這些元件。

我們已經在應用程式中執行過這項措施:

@Composable
fun Header(
  text: String,
  modifier: Modifier = Modifier
) {
  Surface(
    color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
    contentColor = MaterialTheme.colors.primary,
    modifier = modifier.semantics { heading() }
  ) {
    Text(
      text = text,
      style = MaterialTheme.typography.subtitle2,
      modifier = Modifier
        .fillMaxWidth()
        .padding(horizontal = 16.dp, vertical = 8.dp)
    )
  }
}

Header 可組合項基本上是樣式化的 Text,可用於整個應用程式中。

我們發現所有元件都是使用較低層級的建構元素所建構,因此您可以使用相同的建構模塊來自訂質感設計的元件。舉例來說,我們發現 Button 會使用 ProvideTextStyle 可組合項作為傳遞的內容設定預設文字樣式。您可以按照相同的機制自行設定文字樣式:

@Composable
fun LoginButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonConstants.defaultButtonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier
    ) {
        ProvideTextStyle(...) { // set our own text style
            content()
        }
    }
}

在本範例中,我們會納入標準 Button 類別,並指定不同的 backgroundColor 和文字樣式等特定屬性,建立自己的 LoginButton「樣式」。

沒有預設樣式的概念,例如自訂元件類型預設外觀的方法。您也可以藉著建立可納入及自訂程式庫元件的元件來完成。舉例來說,假設您想自訂應用程式中所有 Button 的形狀,但不想變更小型形狀主題,進而影響其他 (非 Button) 元件。為此,請建立自己的可組合項,並在以下情況下使用:

@Composable
fun AcmeButton(
  // expose Button params consumers should be able to change
) {
  val acmeButtonShape: Shape = ...
  Button(
    shape = acmeButtonShape,
    // other params
  )
}

9. 恭喜

恭喜,您已成功完成本程式碼研究室,以及設定 Jetpack Compose 應用程式的風格!

您已實作質感設計主題,並自訂整個應用程式中使用的色彩、字體和形狀,以傳達品牌形象、提高一致性。您已新增支援淺色和深色主題。

後續步驟

請參閱 Compose 課程中的其他程式碼研究室:

其他資訊

範例應用程式

  • 展示多個主題的 Owl
  • 展示動態主題設定功能的 Jetcaster
  • 展示導入自訂設計系統的 Jetsnack

參考文件