Compose 中的材質設計 2

Jetpack Compose 可用於實作質感設計,後者是一套用來打造數位介面的全方位設計系統。Material Design 元件 (按鈕、資訊卡、切換按鈕等) 是 以 Material Design 主題設定為基礎建構而成,能以系統化的方式 自訂 Material Design,以更準確地呈現產品品牌。材料 主題包含顏色字體排版形狀屬性。自訂這些特徵時 屬性,變更就會自動反映在您使用的元件中 建構應用程式

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 用來建立 Material Design 色彩系統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 = MaterialTheme.colors.surface,
    contentColor = contentColorFor(color),
    // ...
) { /* ... */ }

TopAppBar(
    backgroundColor = MaterialTheme.colors.primarySurface,
    contentColor = contentColorFor(backgroundColor),
    // ...
) { /* ... */ }

這不僅可讓您設定可組合函式的色彩 內容的預設顏色,其中包含的可組合函式。多個 根據預設,可組合函式會使用這個內容顏色。舉例來說,Text 會根據 父項內容顏色,Icon 則會使用該顏色設定其本身的色彩 著色。

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

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

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

內容 Alpha 值

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

Jetpack Compose 會透過 LocalContentAlpha 執行這項工作。您可以藉由提供值的方式,為階層指定內容 Alpha 值 這個CompositionLocal。 巢狀 可組合項可以使用這個值,將 Alpha 處理方式套用至內容。 例如:TextIcon 根據預設,系統會調整 LocalContentColor 的組合,以使用 LocalContentAlpha。Material Design 會指定某些標準 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_24
        } else {
            R.drawable.ic_moon_24
        }
    ),
    contentDescription = "Theme"
)

高度重疊

在 Material 中,深色主題中高度較高的途徑將獲得 高度疊加層, 就會讓背景變得比較淡平面的高度越高 (提高它的距離) 隱含的光源,途徑顏色就會變得較淺。

使用以下功能時,Surface 可組合函式會自動套用這些疊加層 深色,以及其他使用途徑的 Material 可組合項:

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

應用程式的螢幕截圖,顯示不同元素所用的顏色 (略有不同)
在不同高度等級

圖 5.資訊卡和底部導覽均使用 surface 顏色 設為背景。因為資訊卡和底部導覽 高度不同 顏色 - 資訊卡比背景還淡,底部導覽則為 比卡片更輕。

如果是不涉及 Surface 的自訂情境,請使用 LocalElevationOverlay, CompositionLocal,其中包含 ElevationOverlay 已使用 Surface 元件:

// 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
    }
}

受限的輔色

Material 建議套用受限顏色 深色的口音 也就是偏好使用 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 raleway = FontFamily(
    Font(R.font.raleway_regular),
    Font(R.font.raleway_medium, FontWeight.W500),
    Font(R.font.raleway_semibold, FontWeight.SemiBold)
)

val myTypography = Typography(
    h1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W300,
        fontSize = 96.sp
    ),
    body1 = TextStyle(
        fontFamily = raleway,
        fontWeight = FontWeight.W600,
        fontSize = 16.sp
    )
    /*...*/
)
MaterialTheme(typography = myTypography, /*...*/) {
    /*...*/
}

如果您要在其他地方使用相同的字體,請指定 defaultFontFamily parameter 並省略任何 TextStyle 元素的 fontFamily

val typography = Typography(defaultFontFamily = raleway)
MaterialTheme(typography = typography, /*...*/) {
    /*...*/
}

使用文字樣式

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

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

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

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

形狀

Material 會定義 形狀系統可讓您 定義大型、中型和小型元件的形狀。

顯示各種 Material Design 形狀

圖 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, /*...*/) {
    /*...*/
}

根據預設,許多元件都會使用這些形狀。例如: ButtonTextFieldFloatingActionButton 預設為小型 AlertDialog 預設為中等 ModalDrawer 預設值為大 — 請參閱 形狀配置參考資料 才能完整對應

使用形狀

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

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

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

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

預設樣式

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

@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 可組合函式。由於 MaterialTheme 會將顏色、字體排版和形狀預設為目前的主題值。如果主題只設定其中一項參數,其他參數會保留預設值。

此外,將以檢視模式為基礎的畫面遷移至 Compose 時,請留意 android:theme 屬性的使用情形。您可能需要新的 MaterialTheme

在此範例中,大部分的畫面使用 PinkTheme,相關區段則使用 BlueTheme。請見下方螢幕截圖和程式碼。

圖 11. 巢狀主題。

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

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

元件狀態

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

可組合項通常有 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 (右) 的按鈕,使用調整後的顏色和高度值。

漣漪效果

Material 元件會透過分享關係圖來表示正在互動。如果 如果您在階層中使用 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 提供不同分享關係圖的按鈕。

瞭解詳情

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

程式碼研究室

影片