Jeptpack Compose 官網(wǎng)教程學(xué)習(xí)筆記(四)主題

主題

主要學(xué)習(xí)內(nèi)容

  • Material Design 入門指南以及如何針對您的品牌對其進(jìn)行自定義
  • Compose 如何實(shí)現(xiàn) Material Design 系統(tǒng)
  • 如何在應(yīng)用中定義和使用顏色庵佣、排版和形狀
  • 如何設(shè)置組件的樣式
  • 如何支持淺色主題和深色主題

在本次學(xué)習(xí)中我們將設(shè)置新聞閱讀應(yīng)用的樣式义钉,從未設(shè)置樣式的應(yīng)用入手,應(yīng)用所學(xué)的內(nèi)容來設(shè)置應(yīng)用的主題猜煮,并為設(shè)置深色主題提供支持


構(gòu)建前:未設(shè)置樣式的應(yīng)用
構(gòu)建后:已設(shè)置樣式的應(yīng)用
構(gòu)建后:深色主題

準(zhǔn)備工作

官網(wǎng)示例下載

因?yàn)橹蟮拇a都是基于其中的項(xiàng)目進(jìn)行的蹭睡,所以還是推薦下載诸衔。同時也可以看一下Google人員對于的Compose的代碼編寫風(fēng)格

因?yàn)榇a過多且需要添加drawable資源文件尼酿,此處就不將代碼寫出來了

在解壓文件中的ThemingCodelab 目錄中存放本次學(xué)習(xí)的案例代碼

此項(xiàng)目包含 3 個主要軟件包:

  • com.codelab.theming.data - 該軟件包包含模型類和示例數(shù)據(jù)恭理,無需修改該軟件包
  • com.codelab.theming.ui.start - 該 示例 的起點(diǎn)抹蚀,您應(yīng)該在該軟件包中完成此 示例 中要求的所有更改
  • com.codelab.theming.ui.finish - 該軟件包是此 示例 的最終狀態(tài)剿牺,供參考

我們可以選擇Import Project方式進(jìn)行學(xué)習(xí),也可以通過拷貝代碼到自己項(xiàng)目中的方式

我使用的是拷貝代碼的方式环壤,可能之后跟Import Project方式有些區(qū)別請諒解

如果是選擇拷貝代碼的方式請注意:

Empty Activity默認(rèn)Activity繼承androidx.appcompat.app.AppCompatActivity晒来,而要使用 Compose 則需要Activity繼承androidx.activity.ComponentActivity,否則會產(chǎn)生如下異常

Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

Material主題設(shè)置

Jetpack Compose 提供了 Material Design 的實(shí)現(xiàn)郑现,Material Design 是一個用于創(chuàng)建數(shù)字化界面的綜合設(shè)計(jì)體系湃崩。Material Design 組件(按鈕、卡片接箫、開關(guān)等)在Material Theme 設(shè)置的基礎(chǔ)上構(gòu)建而成攒读,一個 Material Theme由顏色、排版和形狀屬性組成

顏色

Material Theme 定義了一些從語義上命名的顏色辛友,我們可以在應(yīng)用中使用:

Material Theme 調(diào)色板

其中 primary 是應(yīng)用的主要顏色薄扁,secondary 用于提供強(qiáng)調(diào)色,我們可以通過這兩種顏色的設(shè)置凸顯出需要對比區(qū)域

backgroundsurface兩種顏色用于在概念上駐留在"應(yīng)用表面"的組件的容器废累,也就是背景顏色

Material Design 中還定義了on顏色邓梅,是與具名顏色產(chǎn)生明顯對比的顏色

例如:采用surface作為背景顏色的容器中文本應(yīng)該采用onSurface顏色

Material 組件已配置并使用這些主題顏色。例如邑滨,FloatingActionButton的默認(rèn)顏色為 secondary日缨,Card的默認(rèn)顏色為 surface,諸如此類掖看。

排版

同樣殿遂,Material Theme還定義了一些從語義上命名的字體樣式:

Material Theme 排版

雖然我們可能不會按主題來更改字體樣式诈铛,但使用 Material Design 中的字體樣式可提升應(yīng)用內(nèi)部的一致性

Material 組件已配置并使字體樣式。例如墨礁,TopAppBar默認(rèn)使用 h6 樣式,Button默認(rèn)使用 button樣式耳峦,諸如此類恩静。

形狀

Material Theme 中定義了 3 個類別:小型、中型和大型組件蹲坷;每種組件都可以定義要使用的形狀驶乾,從而自定義角的樣式(切角和圓角)和大小

Material Theme 形狀

默認(rèn)情況下,ButtonTextField使用小型形狀主題循签,CardDialog使用中型形狀主題级乐,Sheet使用大型形狀主題

如需查看組件和形狀主題的完整對應(yīng)關(guān)系,請點(diǎn)擊此處

基準(zhǔn)

Material 默認(rèn)采用“基準(zhǔn)”主題县匠,即紫色的配色方案风科、Roboto 字體比例,以及以上圖片所示的略呈圓形的形狀乞旦。如果您未指定或自定義主題贼穆,組件就會使用基準(zhǔn)主題

定義主題

MaterialTheme

在 Jetpack Compose 中實(shí)現(xiàn)主題設(shè)置的核心元素是 MaterialTheme 可組合項(xiàng)。如果將此可組合項(xiàng)放在 Compose 層次結(jié)構(gòu)中兰粉,您就可以為其中的所有組件指定顏色故痊、字體和形狀的自定義設(shè)置

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

我們可以使用 MaterialTheme object 檢索傳遞到此可組合項(xiàng)的參數(shù),以公開 colors玖姑、typographyshapes 屬性愕秫。在之后,我們將逐一進(jìn)行深入介紹

找到 Home 可組合函數(shù) - 這是應(yīng)用的主入口點(diǎn)焰络。請注意戴甩,雖然我們聲明了 MaterialTheme,但并未指定任何參數(shù)舔琅,因此會獲得默認(rèn)的“基準(zhǔn)”樣式:

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

創(chuàng)建主題

如果需集中設(shè)置樣式等恐,官方建議創(chuàng)建自己的可組合項(xiàng),用于封裝和配置 MaterialTheme

這樣做备蚓,我們就可以在指定自己的主題自定義設(shè)置课蔬,并在多個位置(例如跨多個屏幕或 @Preview)輕松地重復(fù)使用這些自定義設(shè)置

可以根據(jù)需要創(chuàng)建多個主題可組合項(xiàng)。例如郊尝,如果您想針對應(yīng)用的不同部分支持不同的樣式

我們可以在Theme.kt中添加一個名為StudyTheme新可組合函數(shù)

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

如果使用Import Project二跋,在start中沒有Theme.kt文件,需要新建Theme.kt文件

我們回到 Home 可組合函數(shù)流昏,并將 MaterialTheme 替換為 StudyTheme

@Composable
fun Home() {
  ...
  StudyTheme {
    Scaffold(...){...}
  }
}

同樣在PostItemPreviewFeaturedPostPreview 以使用新的 StudyTheme 可組合項(xiàng)來封裝其內(nèi)容扎即,以便預(yù)覽使用新的主題

@Preview("Post Item")
@Composable
private fun PostItemPreview() {
    val post = remember { PostRepo.getFeaturedPost() }
    StudyTheme{
        Surface {
            PostItem(post = post)
        }
    }
}

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

顏色

我們要在應(yīng)用中實(shí)現(xiàn)的調(diào)色板如下所示:

調(diào)色板

Compose 中的顏色是使用 Color 類定義的吞获。借助多個構(gòu)造函數(shù),您可以將顏色指定為 ULong谚鄙,也可以按單獨(dú)的顏色通道來指定顏色

若要從用于指定顏色的常用“#dd0d3c”格式進(jìn)行轉(zhuǎn)換各拷,請將“#”替換為“0xff”,即 Color(0xffdd0d3c)闷营,其中“ff”表示完整的 Alpha 值

Color.kt中添加以下顏色:

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

在定義顏色時烤黍,我們要根據(jù)顏色值“字面意義”命名顏色,而不要“從語義上”命名顏色

例如傻盟,命名為 Red500 而不是 primary速蕊。這樣一來,我們就可以定義多個主題娘赴。例如规哲,在深色主題中或樣式設(shè)置不同的屏幕上,系統(tǒng)可能會將另一種顏色視為 primary

注意導(dǎo)入 Compose 的 Color 類型是 androidx.compose.ui.graphics.Color诽表,而不是 android.graphics.Color

如果使用Import Project唉锌,在start中沒有Color.kt文件,需要新建Color.kt文件

現(xiàn)在关顷,我們已經(jīng)定義了應(yīng)用的顏色糊秆。接下來,我們將其合并到 MaterialTheme 所需的 Colors 對象中议双,從而將特定顏色分配到 Material 的具名顏色痘番。切換回 Theme.kt,然后添加以下代碼:

//為外部屬性平痰,不是StudyTheme中的臨時變量
private val LightColors = lightColors(
    primary = Red700,
    primaryVariant = Red900,
    onPrimary = Color.White,
    secondary = Red700,
    secondaryVariant = Red900,
    onSecondary = Color.White,
    error = Red800
)

下面汞舱,我們要使用 lightColors 函數(shù)來構(gòu)建 Colors,這樣即可提供合理的默認(rèn)值宗雇,讓我們不必將構(gòu)成 Material 調(diào)色板的所有顏色全都指定出來

例如昂芜,我們尚未指定 background 顏色或許多“on”顏色,我們將會使用lightColors中的默認(rèn)值

我們在StudyTheme中使用這些顏色:

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

此時刷新預(yù)覽赔蒲,就會發(fā)現(xiàn)新的配色方案會反映在 TopAppBar 等組件中

排版

我們要在應(yīng)用中實(shí)現(xiàn)的字體樣式如下所示:

字體樣式表

在 Compose 中泌神,我們可以定義 TextStyle 對象,以定義設(shè)置一些文本的樣式所需的信息舞虱。下面是其屬性的示例:

@Immutable
class TextStyle(
    val color: Color = Color.Unspecified,
    val fontSize: TextUnit = TextUnit.Unspecified,
    val fontWeight: FontWeight? = null,
    val fontStyle: FontStyle? = null,
    val fontSynthesis: FontSynthesis? = null,
    val fontFamily: FontFamily? = null,
    val fontFeatureSettings: String? = null,
    val letterSpacing: TextUnit = TextUnit.Unspecified,
    val baselineShift: BaselineShift? = null,
    val textGeometricTransform: TextGeometricTransform? = null,
    val localeList: LocaleList? = null,
    val background: Color = Color.Unspecified,
    val textDecoration: TextDecoration? = null,
    val shadow: Shadow? = null,
    val textAlign: TextAlign? = null,
    val textDirection: TextDirection? = null,
    val lineHeight: TextUnit = TextUnit.Unspecified,
    val textIndent: TextIndent? = null
){ ... }

我們所需的字體比例要針對標(biāo)題使用 Montserrat欢际,并針對正文文本使用 Domine

相關(guān)字體文件在 示例 的 res/font 文件夾中

Type.kt文件中定義 FontFamily(結(jié)合了每個 Font 的不同粗細(xì)):

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)
)

然后創(chuàng)建一個 MaterialTheme 接受的 Typography 對象,為比例中的每個語義樣式指定 TextStyle

val StudyTypography = 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
    )
)

我們在StudyTheme中使用新的 Typography

@Composable
fun StudyTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        content = content,
        colors = LightColors,
/*+*/   typography = StudyTypography
    )
}

形狀

Compose 提供了 RoundedCornerShape 類和 CutCornerShape 類矾兜,可用于定義形狀主題

Shape.kt损趋,并添加以下代碼:

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

我們在StudyTheme中使用新的 Shapes

@Composable
fun StudyTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        content = content,
        colors = LightColors,
        typography = StudyTypography,
/*+*/   shapes = StudyShapes
    )
}

刷新預(yù)覽,可以看見精選博文的 Card 變?yōu)樽笊锨薪切螤?/p>

image-20220517113932065.png

深色主題

在應(yīng)用中支持深色主題不僅有助于您的應(yīng)用在用戶設(shè)備上更好地集成(從 Android 10 開始椅寺,設(shè)備上已提供全局深色主題切換開關(guān))浑槽,還有助于降低能耗以及為滿足無障礙功能需求提供支持蒋失。Material 提供了關(guān)于如何創(chuàng)建深色主題的接口

以下是我們想為深色主題實(shí)現(xiàn)的調(diào)色板:

深色主題調(diào)色板

打開 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
)

darkColorslightColors一樣,當(dāng)我們沒有提供調(diào)色板顏色時提供默認(rèn)值

然后,更新 StudyTheme

@Composable
fun StudyTheme(
/*+*/darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        content = content,
/*+*/   colors = if (darkTheme) DarkColors else LightColors,
        typography = StudyTypography,
        shapes = StudyShapes
    )
}

此時,我們添加了用于判斷是否使用深色主題的新參數(shù),并將其默認(rèn)設(shè)為查詢設(shè)備的全局設(shè)置

FeaturedPost 可組合項(xiàng)創(chuàng)建新的預(yù)覽,此預(yù)覽能夠以深色主題顯示該可組合項(xiàng):

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

預(yù)覽效果對比

預(yù)覽效果對比

處理顏色

我們現(xiàn)在可以創(chuàng)建自己的Theme址晕,設(shè)置應(yīng)用的顏色、字體樣式唱遭、形狀浙垫。而所有的Material 組件默認(rèn)支持這些自定義屬性

例如,FloatingActionButton 可組合項(xiàng)默認(rèn)使用主題中的 secondary 顏色梧油,當(dāng)然我們可以通過為此參數(shù)指定不同的值來設(shè)置顏色:

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

原色

Compose 提供了一個 Color 類苫耸。您可以在本地創(chuàng)建這些類,并將其保留在 object 等元素中:

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

注意:在靜態(tài)聲明顏色定義時儡陨,請務(wù)必小心褪子,因?yàn)檫@些定義會導(dǎo)致更難/無法支持不同的主題(例如,淺色/深色主題)

Color 中有許多有用的方法骗村,例如 copy嫌褪,您可以通過此方法使用不同的 alpha/red/green/blue 值來創(chuàng)建新的顏色

主題顏色

我們可以從主題中檢索顏色

Surface(color = MaterialTheme.colors.primary)

通過使用 MaterialTheme object,其colors屬性會返回MaterialTheme可組合項(xiàng)中設(shè)置的Colors胚股。也就是說笼痛,我們只需為主題提供不同的顏色集,即可支持不同的外觀和風(fēng)格琅拌,而無需處理應(yīng)用代碼

由于主題中的每種顏色都是Color實(shí)例缨伊,因此我們可以通過copy派生出不同的顏色

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

這種方法可以確保顏色可以在不同主題下正常顯示,無需編寫靜態(tài)顏色代碼

背景色和內(nèi)容顏色

許多組件都接受一對顏色和“內(nèi)容顏色”:

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

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

這樣我們不僅可以設(shè)置可組合項(xiàng)的背景顏色进宝,還可以為"內(nèi)容"(即包含在內(nèi)的可組合項(xiàng))提供默認(rèn)顏色刻坊。默認(rèn)情況下,許多可組合項(xiàng)都會使用這種內(nèi)容顏色党晋,如:Text谭胚、Icon

contentColorFor 方法可以為任何主題顏色檢索適當(dāng)?shù)摹皁n”顏色,例如未玻,如果您設(shè)置 primary 背景灾而,它就會返回 onPrimary 作為內(nèi)容顏色。如果您設(shè)置非主題背景顏色深胳,則應(yīng)自行提供合理的內(nèi)容顏色

跟蹤contentColorFor方法最終會進(jìn)入該方法绰疤,所以如果backgroundColor使用非主題顏色,需要提供contentColor

fun Colors.contentColorFor(backgroundColor: Color): Color {
    return when (backgroundColor) {
        primary -> onPrimary
        primaryVariant -> onPrimary
        secondary -> onSecondary
        secondaryVariant -> onSecondary
        background -> onBackground
        surface -> onSurface
        error -> onError
        else -> Color.Unspecified
    }
}

我們還可以使用 LocalContentColor CompositionLocal 來檢索與當(dāng)前背景形成對比的顏色:

如果對于CompositionLocal很陌生舞终,可以看看我的另一篇簡書 簡書-CompositionLocal

BottomNavigationItem(
    unselectedContentColor = LocalContentColor.current 
    ...
  ){ ... }

當(dāng)設(shè)置任何元素的顏色時轻庆,最好使用 Surface 來實(shí)現(xiàn)此目的癣猾,因?yàn)樗鼤O(shè)置適當(dāng)?shù)膬?nèi)容顏色 CompositionLocal

請慎用直接 Modifier.background 調(diào)用,這種調(diào)用不會設(shè)置適當(dāng)?shù)膬?nèi)容顏色

目前余爆,我們的 Header 組件background始終使用 Color.LightGray 背景纷宇。這在淺色主題中看起來沒有問題,但在深色主題中蛾方,就會與背景形成高度對比像捶。而且也不會指定與背景顏色形成對比的文本顏色

//使用uiMode,設(shè)置預(yù)覽時使用深色主題
@Preview("Home",group="Home",uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun HomePreview() {
    Home()
}
image-20220517132639268.png

接下來桩砰,我們通過Surface去解決這個問題

Header 可組合項(xiàng)中拓春,移除用于指定硬編碼顏色的 background 修飾符。改為將 Text 封裝在包含主題派生顏色的 Surface 中亚隅,并指定相應(yīng)內(nèi)容應(yīng)采用 primary 顏色:

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

內(nèi)容 Alpha 值

通常情況下硼莽,我們通過強(qiáng)調(diào)或弱化內(nèi)容來突出重點(diǎn)并體現(xiàn)出視覺上的層次感。Material Design 建議采用不同的不透明度來傳達(dá)這些不同的重要程度

Jetpack Compose 通過 LocalContentAlpha 實(shí)現(xiàn)此功能煮纵。您可以通過為此 CompositionLocal 提供一個值來為層次結(jié)構(gòu)指定內(nèi)容 Alpha 值

子可組合項(xiàng)可以使用此值懂鸵,例如 Text 和 Icon 默認(rèn)使用LocalContentColor的顏色值 ,其中的Alpha會調(diào)整為 LocalContentAlpha的值

@Composable
fun Text(...){
    val textColor = color.takeOrElse {
        style.color.takeOrElse {
            LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
        }
    }
    ...
}

@Composable
fun Icon(
    ...
    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
){ ... }

Material 指定了一些標(biāo)準(zhǔn) Alpha 值(high行疏、medium匆光、disabled),這些值由 ContentAlpha 對象提供

object ContentAlpha {
    val high: Float
        @Composable
        get()=...
    
    val medium: Float
        @Composable
        get()=...
    
    val disabled: Float
        @Composable
        get()=...
}

請注意酿联,MaterialTheme 默認(rèn)將 LocalContentAlpha 設(shè)置為 ContentAlpha.high

我們將使用內(nèi)容 Alpha 值來闡明精選博文的信息層次結(jié)構(gòu)终息。在 PostMetadata 可組合項(xiàng)中,重點(diǎn)突出元數(shù)據(jù) medium

@Composable
private fun PostMetadata(
    post: Post,
    modifier: Modifier = Modifier
) {
    ...
/*+*/CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
        Text(
            text = text,
            modifier = modifier
        )
/*+*/}
}

深色主題

若要在 Compose 中實(shí)現(xiàn)深色主題货葬,我們只需提供不同的顏色集并通過主題查詢顏色即可

我們可以通過下面代碼檢測是否在淺色主題中運(yùn)行:

val isLightTheme = MaterialTheme.colors.isLight

此值由 lightColors/[darkColors 構(gòu)建器函數(shù)設(shè)置

Material Design 建議避免在深色主題中使用大面積的明亮顏色

一種常見模式是在淺色主題中將容器背景顏色設(shè)為 primary 采幌,并在深色主題中將其設(shè)為 surface ;許多組件都默認(rèn)使用此策略震桶,例如TopAppBarBottomNavigation

為了便于實(shí)現(xiàn)休傍,Colors 提供了 primarySurface 顏色,以準(zhǔn)確完成上述行為

@Composable
fun BottomNavigation(
    ...
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    ...
){ ... }

@Composable
fun TopAppBar(
    ...
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    ...
){ ... }
val Colors.primarySurface: Color get() = if (isLight) primary else surface

遵循此指南蹲姐,我們需要將AppBar中的TopAppBarbackgroundColor 切換為primarySurface或移除此參數(shù)(因?yàn)榇藚?shù)為默認(rèn)設(shè)置)即可

@Composable
private fun AppBar() {
    TopAppBar(
        navigationIcon = {
            Icon(
                imageVector = Icons.Rounded.Palette,
                contentDescription = null,
                modifier = Modifier.padding(horizontal = 12.dp)
            )
        },
        title = {
            Text(text = stringResource(R.string.app_title))
        },
        //更換為primarySurface或直接刪除即可
        backgroundColor = MaterialTheme.colors.primarySurface
    )
}

在 Material 中磨取,如果采用的是深色主題,則高度較高 (elevation) 的 Surface 會獲得高度疊加層(其背景顏色會變淺)柴墩。在使用深色主題時忙厌,系統(tǒng)會自動實(shí)現(xiàn)此效果:

高度疊加層效果

處理文本

在處理文本時,我們使用 Text 可組合項(xiàng)來顯示文本江咳,使用 TextFieldOutlinedTextField 進(jìn)行文本輸入逢净,并使用 TextStyle 對文本應(yīng)用單一樣式。我們可以使用 AnnotatedString 對文本應(yīng)用多種樣式

和顏色一樣,用于顯示文本的 Material 組件可以獲取到主題排版自定義設(shè)置:

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

不過實(shí)現(xiàn)此目的要比使用默認(rèn)參數(shù)(如在設(shè)置顏色時所看到的那樣)略微復(fù)雜一些

因?yàn)榻M件本身往往不會顯示文本爹土,而是提供槽 API甥雕,讓您能夠傳入 Text 可組合項(xiàng)。那么胀茵,組件是如何設(shè)置主題排版樣式的呢社露?

在后臺,它們使用 ProvideTextStyle 可組合項(xiàng)(本身就使用 CompositionLocal)來設(shè)置"current"TextStyle琼娘。如果您未提供具體的 textStyle 參數(shù)峭弟,Text 可組合項(xiàng)會默認(rèn)查詢此"current"樣式

@Composable
fun Button(
    ...
) {
    val contentColor by colors.contentColor(enabled)
    Surface(
        ...
    ) {
        CompositionLocalProvider(LocalContentAlpha provides contentColor.alpha) {
            ProvideTextStyle(
                value = MaterialTheme.typography.button
            ) {
                ...
            }
        }
    }
}

@Composable
fun Text(
    ...
    style: TextStyle = LocalTextStyle.current
) { ... }

主題文本樣式

就像顏色一樣,最好從當(dāng)前主題中檢索 TextStyle脱拼,使用一組數(shù)量少且一致的樣式瞒瘸,并使其更易于維護(hù)

MaterialTheme.typography 會檢索在 MaterialTheme 可組合項(xiàng)中設(shè)置的 Typography 實(shí)例,讓我們能夠使用自己定義的樣式:

Text(
  style = MaterialTheme.typography.subtitle2
)

如果您需要自定義 TextStyle熄浓,可以對其執(zhí)行 copy 操作并替換相關(guān)屬性挨务,或者讓 Text 可組合項(xiàng)接受大量樣式參數(shù),這些參數(shù)會疊加到任何 TextStyle 的上層:

//使用copy操作替換屬性
Text(
  text = "Hello World",
  style = MaterialTheme.typography.body1.copy(
    background = MaterialTheme.colors.secondary
  )
)
// 使用樣式參數(shù)覆蓋樣式中的值
Text(
  text = "Hello World",
  style = MaterialTheme.typography.subtitle2,
  fontSize = 22.sp 
)

在我們的應(yīng)用中玉组,許多地方都會自動應(yīng)用主題 TextStyle,例如丁侄,TopAppBar 將其 title 的樣式設(shè)為 h6惯雳,而 ListItem將其主要文本和輔助文本的樣式分別設(shè)為 subtitle1body2

接下來,我們要將主題排版樣式應(yīng)用于應(yīng)用的其余部分鸿摇。將 Header 設(shè)為使用 subtitle2石景;對于 FeaturedPost 中的文本,將標(biāo)題設(shè)為 h6拙吉,并將作者信息和PostMetadata設(shè)為 body2

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

多種樣式

如果您需要對某些文本應(yīng)用多種樣式潮孽,可以使用 AnnotatedString 類來應(yīng)用標(biāo)記,從而為一系列文本添加 SpanStyle筷黔。您可以動態(tài)添加這些元素往史,也可以使用 DSL 語法來創(chuàng)建內(nèi)容:

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")
  }
}

接下來,我們要為描述應(yīng)用中的各個博文的標(biāo)簽設(shè)置樣式佛舱。目前椎例,它們使用與元數(shù)據(jù)其余部分相同的文本樣式;我們將使用 overline 文本樣式和背景顏色來區(qū)分它們请祖。在 PostMetadata 可組合項(xiàng)中:

@Composable
private fun PostMetadata(
    post: Post,
    modifier: Modifier = Modifier
) {
    val divider = "  ?  "
    val tagDivider = "  "
    val text = buildAnnotatedString {
        append(post.metadata.date)
        append(divider)
        append(stringResource(R.string.read_time, post.metadata.readTimeMinutes))
        append(divider)

/*+*/   val tagStyle=MaterialTheme.typography.overline.toSpanStyle().copy(
/*+*/       background = MaterialTheme.colors.primary.copy(alpha = 0.1f)
/*+*/   )
        post.tags.forEachIndexed { index, tag ->
            if (index != 0) {
                append(tagDivider)
            }
/*+*/       withStyle(tagStyle){
                append(" ${tag.uppercase(Locale.getDefault())} ")
/*+*/       }
        }
    }
    ...
}
image-20220517151906032.png

處理形狀

與顏色和排版一樣订歪,如果設(shè)置形狀主題,相應(yīng)設(shè)置會反映在 Material 組件中肆捕。例如刷晋,Button 會獲取為小型組件設(shè)置的形狀:

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

與顏色一樣,Material 組件使用默認(rèn)參數(shù),我們可以直接查看組件將要使用的形狀類別眼虱,或提供替代方案喻奥。如需查看組件和形狀類別的完整對應(yīng)關(guān)系,請參閱此文檔蒙幻。

請注意映凳,有些組件會使用經(jīng)過修改的主題形狀,以適應(yīng)其上下文的要求邮破。例如诈豌,默認(rèn)情況下,TextField 使用小型形狀主題抒和,但它會對底角應(yīng)用零邊角大薪糜妗:

@Composable
fun TextField(
    ...
    shape: Shape =
        MaterialTheme.shapes.small.copy(
            bottomEnd = ZeroCornerSize, 
            bottomStart = ZeroCornerSize
        ),
    ...
) { ... }

主題形狀

在創(chuàng)建自己的組件時,我們可以自行使用各種形狀摧莽;為此庙洼,需要使用接受形狀的可組合項(xiàng)或 Modifier(例如,Surface镊辕、Modifier.clip油够、Modifier.backgroundModifier.border 等)

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

接下來征懈,我們要將形狀主題添加到 PostItem 中顯示的圖片石咬;我們要對其應(yīng)用主題的 small 形狀,并使用 Modifier.clip 應(yīng)用該形狀:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun PostItem(
    post: Post,
    modifier: Modifier = Modifier
) {
    ListItem(
        modifier = modifier
            .clickable { /* todo */ }
            .padding(vertical = 8.dp),
        icon = {
            Image(
                painter = painterResource(post.imageThumbId),
                contentDescription = null,
/*+*/           modifier = Modifier.clip(shape = MaterialTheme.shapes.small)
            )
        },
        text = {
            Text(text = post.title)
        },
        secondaryText = {
            PostMetadata(post)
        }
    )
}
image-20220517153124898.png

組件樣式

Compose 沒有提供用于提取組件樣式(例如卖哎,Android View 樣式或 CSS 樣式)的明確方法鬼悠。由于所有 Compose 組件都是用 Kotlin 編寫的,因此還可通過其他方法來實(shí)現(xiàn)相同的目的

我們可以改為創(chuàng)建自己的自定義組件庫亏娜,并在整個應(yīng)用中使用這些組件

比如 示例 中:

@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 可組合項(xiàng)本質(zhì)上是樣式化的 Text焕窝,可供我們在整個應(yīng)用中使用

所有組件都是由較低級別的構(gòu)建塊構(gòu)造而成的,我們可以使用同樣的構(gòu)建塊來自定義 Material 組件

例如维贺, Button 使用 ProvideTextStyle 可組合項(xiàng)為傳遞給它的內(nèi)容設(shè)置默認(rèn)文本樣式它掂。您可以使用完全相同的機(jī)制來設(shè)置自己的文本樣式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市幸缕,隨后出現(xiàn)的幾起案子群发,更是在濱河造成了極大的恐慌,老刑警劉巖发乔,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件熟妓,死亡現(xiàn)場離奇詭異,居然都是意外死亡栏尚,警方通過查閱死者的電腦和手機(jī)起愈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抬虽,你說我怎么就攤上這事官觅。” “怎么了阐污?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵休涤,是天一觀的道長。 經(jīng)常有香客問我笛辟,道長功氨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任手幢,我火速辦了婚禮捷凄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘围来。我一直安慰自己跺涤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布监透。 她就那樣靜靜地躺著桶错,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胀蛮。 梳的紋絲不亂的頭發(fā)上牛曹,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機(jī)與錄音醇滥,去河邊找鬼。 笑死超营,一個胖子當(dāng)著我的面吹牛鸳玩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播演闭,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼不跟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了米碰?” 一聲冷哼從身側(cè)響起窝革,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吕座,沒想到半個月后虐译,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吴趴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年漆诽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡厢拭,死狀恐怖兰英,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情供鸠,我是刑警寧澤畦贸,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站楞捂,受9級特大地震影響薄坏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泡一,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一颤殴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼻忠,春花似錦涵但、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至塑娇,卻和暖如春澈侠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背埋酬。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工哨啃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人写妥。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓拳球,卻偏偏與公主長得像,于是被迫代替她去往敵國和親珍特。 傳聞我的和親對象是個殘疾皇子祝峻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容