Compose 中的材質主題設定

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

Jetpack Compose 可透過 MaterialTheme 可組合項實作這些概念:

MaterialTheme(
    colors = …,
    typography = …,
    shapes = …
) {
    // app content
}

設定要傳遞給 MaterialTheme 的參數,以便設定應用程式的主題。

兩張對比的螢幕截圖。第一張使用預設的 MaterialTheme 樣式,第二張螢幕截圖則採用修改後的樣式。

圖 1.第一張螢幕截圖顯示未設定 MaterialTheme 的應用程式,因此採用預設樣式。第二張螢幕截圖顯示會將參數傳遞至 MaterialTheme 以自訂樣式的應用程式。

顏色

顏色是在 Compose 中使用 Color 類別 (簡單的 data-holding 類別) 建立型式。

val Red = Color(0xffff0000)
val Blue = Color(red = 0f, green = 0f, blue = 1f)

雖然您可以依照自己的偏好整理這些資料 (例如頂層常數、單例模式內或定義內嵌),但強烈建議您在主題中指定色彩,並從該處擷取色彩。這個方法可讓您輕鬆支援深色主題和巢狀主題。

主題的調色盤範例

圖 2.材質色彩系統。

Compose 提供 Colors 類別,以建立材質色彩系統的型式。Colors 提供建構工具函式來建立一組淺色深色顏色:

private val Yellow200 = Color(0xffffeb46)
private val Blue200 = Color(0xff91a4fc)
// ...

private val DarkColors = darkColors(
    primary = Yellow200,
    secondary = Blue200,
    // ...
)
private val LightColors = lightColors(
    primary = Yellow500,
    primaryVariant = Yellow400,
    secondary = Blue700,
    // ...
)

定義 Colors 後,您可以將其傳遞至 MaterialTheme

MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors
) {
    // app content
}

使用主題顏色

您可以使用 MaterialTheme.colors 擷取提供給 MaterialTheme 可組合項的 Colors

Text(
    text = "Hello theming",
    color = MaterialTheme.colors.primary
)

途徑和內容顏色

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

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

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

這不僅可讓您設定可組合項的色彩,還能為內容提供預設顏色 (包含在其中的可組合項)。許多可組合項預設會使用這個內容顏色。例如,Text 會根據父項內容的顏色設定顏色,而 Icon 則會使用該顏色設定色調。

同一個橫幅廣告但顏色不同的兩個範例

圖 3.設定不同的背景顏色會產生不同的文字和圖示顏色。

contentColorFor() 方法會擷取任何主題色彩的適當「開啟」顏色。舉例來說,如果您在 Surface 上設定 primary 背景顏色,就會使用此函式將 onPrimary 設為內容顏色。如果您設定了非主題的背景顏色,也應一併指定適當的內容顏色。使用 LocalContentColor 從階層中的指定位置擷取目前背景偏好的內容顏色。

內容 Alpha

通常您會希望以不同的程度強調內容,做為溝通重要性並提供視覺階層的方式。材質設計文字易讀性建議建議採用不同程度的透明度,以傳達不同的重要性等級。

Jetpack Compose 會透過 LocalContentAlpha 執行這項工作。您可以為這個 CompositionLocal 提供值,從而為階層指定內容 Alpha。巢狀可組合項可使用這個值,將 Alpha 處理方式套用至內容。舉例來說,根據預設,TextIcon 會使用 LocalContentColor 的組合調整值,以使用 LocalContentAlpha。材質會指定某些標準 Alpha 值 (highmediumdisabled),此型式是透過 ContentAlpha 物件建立。

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

如要進一步瞭解 CompositionLocal,請參閱使用 CompositionLocal 指南的本機範圍資料

文章標題的螢幕截圖,顯示不同等級的文字重點

圖 4.套用不同層級的文字強調資訊,以視覺化的方式呈現資訊階層。第一行文字是標題且最重要的資訊,因此使用 ContentAlpha.high。第二行包含較不重要的中繼資料,因此使用 ContentAlpha.medium

深色主題

在 Compose 中,為 MaterialTheme 可組合項提供不同的 Colors 組合,以實作淺色和深色主題:

@Composable
fun MyTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) DarkColors else LightColors,
        /*...*/
        content = content
    )
}

在這個範例中,MaterialTheme 會包裝在其可組合函式中,該函式接受指定是否要使用深色主題的參數。在這種情況下,函式會查詢裝置主題設定,藉此取得 darkTheme 的預設值。

你可以使用像這樣的程式碼來檢查目前的 Colors 是淺色還是深色:

val isLightTheme = MaterialTheme.colors.isLight
Icon(
    painterResource(
        id = if (isLightTheme) {
          R.drawable.ic_sun_24dp
        } else {
          R.drawable.ic_moon_24dp
        }
    ),
    contentDescription = "Theme"
)

高度重疊

在材質中,深色主題中高度較高的途徑將取得高度重疊,以此方式調淡背景。途徑的高度越高 (提高使其更接近隱含光源),途徑顏色就會變得較淺。

使用深色時,這些重疊是由 Surface 可組合項自動套用,亦適用於使用途徑的其他材質可組合項:

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

應用程式螢幕截圖,顯示使用不同高度等級的元素所採用的略有不同色彩

圖 5.資訊卡和底部導覽均使用 surface 顏色做為背景。資訊卡和底部導覽位於背景上方的不同高度層級,因此顏色會略有不同:資訊卡比背景顏色淡,底部導覽又比資訊卡更淡。

如果是不涉及 Surface 的自訂情境,請使用 LocalElevationOverlay;也就是包含 ElevationOverlay (由 Surface 可組合項使用) 的 CompositionLocal

// Elevation overlays
// Implemented in Surface (and any components that use it)
val color = MaterialTheme.colors.surface
val elevation = 4.dp
val overlaidColor = LocalElevationOverlay.current?.apply(
    color, elevation
)

如要停用高度重疊,請在可組合階層的適當點提供 null

MyTheme {
    CompositionLocalProvider(LocalElevationOverlay provides null) {
        // Content without elevation overlays
    }
}

受限的輔色

在多數情況下,材質會偏好使用 surface 顏色,並針對深色主題優先套用受限的輔色,而非 primary 顏色。根據預設,材質可組合項 (例如 TopAppBarBottomNavigation) 會實作此行為。

圖 6.使用受限輔色的材質深色主題。上方應用程式列採用淺色主題的主色和深色主題的途徑顏色。

針對自訂情境,請使用 primarySurface 擴充功能屬性:

Surface(
    // Switches between primary in light theme and surface in dark theme
    color = MaterialTheme.colors.primarySurface,
    /*...*/
) { /*...*/ }

字體排版

材質會定義類型系統,並建議您少用語意命名樣式。

各種樣式的多種字體範例

圖 7.材質類型系統。

Compose 會透過 TypographyTextStyle字型相關類別實作類型系統。Typography 建構函式提供每種樣式的預設值,因此您可以略過任何不想自訂的樣式:

val Rubik = FontFamily(
    Font(R.font.rubik_regular),
    Font(R.font.rubik_medium, FontWeight.W500),
    Font(R.font.rubik_bold, FontWeight.Bold)
)

val MyTypography = Typography(
    h1 = TextStyle(
        fontFamily = Rubik,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = Rubik,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = MyTypography, /*...*/)

如果您想在往後使用相同的字體,請指定 defaultFontFamily parameter 並省略任何 TextStyle 元素的 fontFamily

val typography = Typography(defaultFontFamily = Rubik)
MaterialTheme(typography = typography, /*...*/)

使用文字樣式

您可透過 MaterialTheme.typography 存取 TextStyle。擷取 TextStyle,如下所示:

Text(
    text = "Subtitle2 styled",
    style = MaterialTheme.typography.subtitle2
)

螢幕截圖:顯示不同用途的各式字體

圖 8.使用一系列字體和樣式來代表您的品牌。

形狀

材質定義了形狀系統,可讓您定義大型、中型和小型元件的形狀。

顯示各種材質設計形狀

圖 9.材質形狀系統。

Compose 透過 Shapes 類別實作形狀系統,讓您為每個尺寸類別指定 CornerBasedShape

val Shapes = Shapes(
    small = RoundedCornerShape(percent = 50),
    medium = RoundedCornerShape(0f),
    large = CutCornerShape(
        topStart = 16.dp,
        topEnd = 0.dp,
        bottomEnd = 0.dp,
        bottomStart = 16.dp
    )
)

MaterialTheme(shapes = Shapes, /*...*/)

根據預設,許多元件都會使用這些形狀。舉例來說, ButtonTextField 以及 FloatingActionButton 預設為小型、AlertDialog 預設為中型及 ModalDrawer 預設為大型 - 請參閱 形狀配置參考資料中的完整對應資訊。

使用形狀

您可透過 MaterialTheme.shapes 存取 Shape。使用下列程式碼擷取 Shape

Surface(
    shape = MaterialTheme.shapes.medium, /*...*/
) {
    /*...*/
}

使用材質形狀來傳達元素狀態的應用程式螢幕截圖

圖 10.使用形狀來代表品牌或狀態。

預設樣式

Android 檢視畫面中的預設樣式,在 Compose 中沒有對等的概念。您可以自行建立包裝材質元件的「超載」可組合函式,藉此提供類似的功能。舉例來說,如要建立按鈕樣式,請將按鈕包裝在自己的可組合函式中、直接設定您想要變更的參數,然後以參數形式向包含的可組合項公開其他值。

@Composable
fun MyButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
        content = content
    )
}

主題重疊

您可以透過建立 MaterialTheme 元件的巢狀結構,在 Compose 中找到與 Android 檢視畫面主題重疊對等的設定。由於 MaterialTheme 會將顏色、字體排版和形狀預設為目前的主題值,如果主題只設定其中一項參數,其他參數仍會使用預設值。

此外,將以檢視模式為基礎的畫面遷移至 Compose 時,請留意 android:theme 屬性的使用情形。您可能需要在 Compose UI 樹狀結構的該部分中新增 MaterialTheme

Owl 範例中,大部分畫面的詳細資料畫面使用 PinkTheme,相關區段則使用 BlueTheme。請參閱下方的螢幕截圖和程式碼。

圖 11.Owl 範例中的巢狀主題。

@Composable
fun DetailsScreen(/* ... */) {
    PinkTheme {
        // other content
        RelatedSection()
    }
}

@Composable
fun RelatedSection(/* ... */) {
    BlueTheme {
        // content
    }
}

元件狀態

可互動 (已點擊、切換等) 的材質元件可以是不同的視覺狀態。狀態包括已啟用、已停用、已按下等

可組合項通常有 enabled 參數。設為 false 可禁止互動,並變更顏色和高度等屬性,以便透過視覺化方式傳達元件狀態。

圖 12.enabled = true (左) 和 enabled = false (右) 的按鈕。

在大部分情況下,您可以仰賴顏色和高度等預設值。 如果您想設定不同狀態使用的值,可以使用各種類別和便利函式。請參考下方按鈕範例:

Button(
    onClick = { /* ... */ },
    enabled = true,
    // Custom colors for different states
    colors = ButtonDefaults.buttonColors(
        backgroundColor = MaterialTheme.colors.secondary,
        disabledBackgroundColor = MaterialTheme.colors.onBackground
            .copy(alpha = 0.2f)
            .compositeOver(MaterialTheme.colors.background)
        // Also contentColor and disabledContentColor
    ),
    // Custom elevation for different states
    elevation = ButtonDefaults.elevation(
        defaultElevation = 8.dp,
        disabledElevation = 2.dp,
        // Also pressedElevation
    )
) { /* ... */ }

圖 13.包含 enabled = true (左) 和 enabled = false (右) 的按鈕,使用調整後的顏色和高度值。

分享關係圖

材質元件會透過分享關係圖來表示正在互動。如果您在階層中使用 MaterialTheme,則會使用 Ripple 做為預設的 Indication 內部輔助鍵 (例如 clickableindication)。

在大部分情況下,您可以仰賴預設的 Ripple。如想設定外觀,您可以使用 RippleTheme 變更顏色和 Alpha 等屬性。

您可以延伸 RippleTheme,並採用 defaultRippleColordefaultRippleAlpha 公用程式函式。然後,您可以利用 LocalRippleTheme 在階層中提供自訂分享關係圖主題:

@Composable
fun MyApp() {
  MaterialTheme {
    CompositionLocalProvider(
      LocalRippleTheme provides SecondaryRippleTheme
    ) {
      // App content
    }
  }
}

@Immutable
private object SecondaryRippleTheme : RippleTheme {
  @Composable
  override fun defaultColor() = RippleTheme.defaultRippleColor(
    contentColor = MaterialTheme.colors.secondary,
    lightTheme = MaterialTheme.colors.isLight
  )

  @Composable
  override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
    contentColor = MaterialTheme.colors.secondary,
    lightTheme = MaterialTheme.colors.isLight
  )
}

alt_text

圖 14.透過 RippleTheme 提供不同分享關係圖的按鈕。

材質設計 3 和 Material You

Jetpack Compose 提供材質設計 3 的實作作業,正是材質設計的下一次進化。材質 3 包含更新過的主題設定和元件,以及 Material You 個人化功能 (例如動態顏色),在設計上可與全新 Android 12 視覺風格和系統 UI 呼應。

首先,請將新的 Compose 材質 3 依附元件新增至 build.gradle 檔案:

implementation "androidx.compose.material3:material3:material3_version"

M3 主題包含色彩配置字體排版的值,且即將提供形狀更新。自訂這些值時,系統會自動在您用於建構應用程式的 M3 元件中反映變更。

Jetpack Compose 以新的 M3 MaterialTheme 可組合項實作這些概念:

MaterialTheme(
    colorScheme = …,
    typography = …
    // Updates to shapes coming soon
) {
    // M3 app content
}

設定傳遞至 M3 MaterialTheme 的參數,為您的應用程式設定主題。

圖 15.前兩張螢幕截圖顯示尚未設定 M3 MaterialTheme 的應用程式,因此會使用預設樣式。後兩張螢幕截圖顯示將參數傳遞給 MaterialTheme 以自訂樣式的應用程式。

色彩配置

材質設計 3 會將顏色區分至命名色彩的「運算單元」(例如「primary」、「background」和「error」),而這些內容會由材質 3 元件使用。這些運算單元會共同形成色彩配置。每個運算單元使用的色彩值都是從一組色調調色盤中挑選,以滿足無障礙需求 (例如,「primary」運算單元和「on primary」運算單元會呈現對比性)。色彩配置功能會針對淺色和深色主題提供全新的預設基準色彩。

圖 16.材質 3 色調調色盤和色彩配置,包含基準色彩值。

Compose 提供 ColorScheme 類別,建立材質 3 的色彩配置型式。ColorScheme 提供建構工具函式,用於建立淺色深色的色彩配置:

private val Blue40 = Color(0xff1e40ff​​)
private val DarkBlue40 = Color(0xff3e41f4)
private val Yellow40 = Color(0xff7d5700)
// Remaining colors from tonal palettes

private val LightColorScheme = lightColorScheme(
    primary = Blue40,
    secondary = DarkBlue40,
    tertiary = Yellow40,
    // error, primaryContainer, onSecondary, etc.
)
private val DarkColorScheme = darkColorScheme(
    primary = Blue80,
    secondary = DarkBlue80,
    tertiary = Yellow80,
    // error, primaryContainer, onSecondary, etc.
)

定義 ColorScheme 後,即可將其傳遞至 M3 MaterialTheme

val darkTheme = isSystemInDarkTheme()
MaterialTheme(
    colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
) {
    // M3 app content
}

正在產生色彩配置

雖然您可以手動建立自訂 ColorScheme,但使用品牌的來源色彩較容易產生。您可以使用材質主題建構工具來執行這項操作,並視需要匯出 Compose 主題設定程式碼。

圖 17.材質 3 色調調色盤和色彩配置並附自訂色彩值,是由材質主題建構工具產生。

動態色彩配置

動態色彩是 Material You 的關鍵部分,其中演算法會從使用者的桌布衍生自訂色彩,並套用至其應用程式和系統 UI。這個調色盤是用來做為產生完整淺色和深色配置的起點。

動態顏色適用於 Android 12 以上版本。如有動態顏色可用,您可以設定動態 ColorScheme。如果沒有,則可恢復使用自訂的淺色或深色 ColorScheme

ColorScheme 會提供建構工具函式來建立動態淺色深色色彩配置:

// Dynamic color is available on Android 12+
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colorScheme = when {
    dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
    dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
    darkTheme -> DarkColorScheme
    else -> LightColorScheme
}

圖 18.前兩張螢幕截圖顯示,根據紅色桌布使用動態 ColorScheme 的應用程式。後兩張螢幕截圖顯示根據藍色桌布使用動態 ColorScheme 的應用程式。

使用色彩配置顏色

您可以使用 MaterialTheme.colorScheme 擷取提供給 M3 MaterialTheme 可組合項的 ColorScheme

Text(
    text = "Hello M3 theming",
    color = MaterialTheme.colorScheme.tertiary
)

字體排版

材質設計 3 定義了類型調整,包括從材質設計 2 改編的文字樣式。命名和分組功能經過簡化,包括顯示、廣告標題、標題、內文和標籤,有大、中和小三種尺寸。

圖 19.材質 3 類型調整與材質 2 類型調整。

Compose 提供新的 M3 Typography 類別,配合現有的 TextStyle字型相關 類別:可為材質 3 類型調整建立型式:

val KarlaFontFamily = FontFamily(
    Font(R.font.karla_regular),
    Font(R.font.karla_bold, FontWeight.Bold)
)

val AppTypography = Typography(
    bodyLarge = TextStyle(
        fontFamily = KarlaFontFamily,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.15.sp
    ),
    // titleMedium, labelSmall, etc.
)

定義 Typography 後,即可將其傳遞至 M3 MaterialTheme

MaterialTheme(
    typography = AppTypography
) {
    // M3 app content
}

使用文字樣式

您可以使用 MaterialTheme.typography 擷取提供給 M3 MaterialTheme 可組合項的 Typography

Text(
    text = "Hello M3 theming",
    style = MaterialTheme.typography.bodyLarge
)

高度

材質 3 代表主要使用色調色彩重疊的高度。這是區分容器和途徑的新方式,提高色調高度除了陰影之外,需使用較為顯著的色調。

深色主題中的高度重疊也已變更為材質 3 中的色調色彩重疊。

重疊顏色來自主要色彩運算單元。

圖 20.淺色和深色主題中,材質 3 的高度與材質 2 的高度。

M3 Surface 是多數 M3 元件的幕後可組合項,包括色調及陰影高度的支援:

Surface(
    tonalElevation = 16.dp,
    shadowElevation = 16.dp
) {
    // Surface content
}

系統 UI

Material You 的某些面向,來自全新的 Android 12 視覺樣式和系統 UI。兩個主要部分是分享關係圖和過度捲動的變更。您無須採取額外工作就能導入這些變更。

分享關係圖

按下時,分享關係圖現在使用細微的火花來照亮途徑。 Compose 材質分享關係圖在 Android 手機使用 RippleDrawable 平台,因此 Android 12 以上版本所有材質元件均可使用火花分享關係圖。

圖 21.Android 12 分享關係圖與 Android 12 以前的分享關係圖。

過度捲動

過度捲動現在可以在捲動容器的邊緣使用延展效果。在 Compose Foundation 1.1.0 以上版本中,無論 API 級別,在捲動容器可組合項中,延伸過度捲動預設為開啟,例如 LazyColumnLazyRow 以及 LazyVerticalGrid

圖 22.延展過度捲度。

瞭解詳情

如要進一步瞭解 Compose 中的材質主題設定,請參閱下列其他資源。

程式碼研究室

影片