咱们书接上文,
四、处理颜色
在上一步骤中,我们了解了如何创建自己的主题,以为您的应用设置颜色、字体样式和形状。所有Material组件开箱即可使用这些自定义功能。例如,FloatingActionButton
可组合项默认使用主题中的secondary
颜色,但您可以通过为此参数指定不同的值来设置备用颜色”
@Composable
fun FloatingActionButton(
backgroundColor: Color = MaterialTheme.colors.secondary,
...
) {
}
有时,您并不想使用默认证书。
4.1、原色
如前所述,Compose提供了一个Color
类。您可以在本地创建这些类,并将其保留在object
等元素中:
Surface(color = Color.LightGray) {
Text(
text = "Hard coded colors don't respond to theme changes :(",
textColor = Color(0xffff00ff)
)
}
Color中有许多有用的方法,例如copy
,您可以通过此方法使用不同的alpha/red/green/blue
值来创建新的颜色。
4.2、主题颜色
一种更灵活的方法是从主题中检索颜色:
Surface(color = MaterialTheme.colors.primary)
下面,我们要使用MaterialTheme object
,其colors
属性会返回在MaterialTheme
可组合项中设置的Colors
。这意味着,我们只需为主题提供不同的颜色集,即可支持不同的外观和风格,而无需处理应用代码。例如,我们的AppBar
使用primary
颜色,屏幕北京使用surface
颜色;如果更改主题颜色,相应设置会反映在以下可组合项中:
由于主题中的每种颜色都是Color
实例,因此我们还可以使用copy
方法轻松地”派生“颜色:
val deriverColor = MaterialTheme.colors.onSurface.copy(alpha = 0.1f)
下面,我们要复制onSurface
颜色,但要将透明度设为10%。此方法可确保颜色能够在不同主题下正常显示,而无需硬编码静态颜色。
4.3、Surface颜色和内容颜色
许多组件都接受一对颜色和”内容颜色“:
Surface(
color: Color = MaterialTheme.colors.surface,
contentColor: Color = contentColorFor(color),
...
)
TopAppBar(
backgroundColor: Color = MaterialTheme.colors.primarySurface,
contentColor: Color = contentColorFor(backgroundColor),
...
)
这样一来,您不仅可以设置可组合项的颜色,而且还能为“内容”(即包含在其中的可组合项)提供默认颜色。默认情况下,许多可组合项都使用这种颜色,例如Text
颜色或Icon
色调。contentColorFor
方法可以为任何主题颜色检索适当的”on”颜色,例如,如果您设置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
背景。在这浅色主题中看起来没有问题,但在深色主题中,就会与背景成高度对比。此外,它们也不指定特定的文本颜色,因此会集成可能不会与背景形成对比的当前内容颜色:
接下来,让我们解决这个问题。在Home.kt
的Header
可组合项中,移出用于指定硬编码颜色的background
修饰符。改为将Text
封装在包含主题派生颜色的Surface
中,并指定相应内容应采用primary
颜色:
+ Surface(
+ color = MaterialTheme.colors.onSurface.copy(alpha = 0.1f),
+ contentColor = MaterialTheme.colors.primary,
+ modifier = modifier
+ ) {
Text(
text = text,
modifier = Modifier
.fillMaxWitdh()
- .background(Color.LightGray)
.padding(horizontal = 16.dp, vertical = 8.dp)
)
+}
4.4、内容Alpha值
通常情况下,我们希望通过强调或弱化内容来突出重点并体现出视觉上的层次感。Material Design建议采用不同的透明度来传达这些不同的重要程度。
Jetpack Compose通过LocalContentAlpha
实现此功能。您可以通过为此CompositionLocal
提供一个值来为层次结构指定内容Alpha值。子可组合项可以使用此值,例如Text
和Icon
默认使用LocalContentColor
的组合,已调整为使用LocalContentAlpha
。Material指定了一些标准Alpha值(high
、medium
、disabled
),这些值由ContentAlpha
对象建模。请注意,MaterialTheme
默认将LocalContentAlpha
设置为ContentAlpha.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
:
+ CompositioLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(
text = text,
modifier = modifier
)
+}
### 4.5、深色主题
如我们所见,若要在Compose中实现深色主题,您只需提供不同的颜色集并通过主题查询颜色即可。下面是一些需要注意的例外情况:
您可以检查您是否在浅色主题中运行:
val isLightTheme = MaterialTheme.colors.isLight
此值由lightColors/darkColors
构建器函数设置。
在Material中,如果采用的是深色主题,高度较高的Surface会获得高度层加层(其背景颜色会变浅)。
在使用深色调色板时,系统会自动实现此效果:
Surface(
elevation = 2.dp,
color = MaterialTheme.colors.surface, // color will be adjusted for elevation
...
在我们的应用中,我们可以在我们使用的TopAppBar
和Card
组件中看到上述自动行为;默认情况下,这两种组件的高度分别设为4dp和1dp,因此,在深色主题中,它们的背景颜色会自动变浅,以更好地表现相应高度:
Material Design建议避免在深色主题中使用大面积的明亮颜色。一种常见模式是在浅色主题中将容器设为primary
颜色,并在深色追中将其设为surface
颜色;许多组件都默认使用此策略,例如应用栏和底部导航栏。为了便于实现,Colors
提供了primarySurface
颜色,以准确完成上述行为,并且这些组件都默认使用此颜色。
目前,我们的应用将应用栏设置为primary
颜色;若要遵循此指南,只需将其切换为primarySurface
或移出此形参(因为此形参为默认设置)即可。在AppBar
可组合项中,更改TopAppBar
的backgroundColor
参数:
@Composable
private fun AppBar() {
TopAppBar(
...
- backgroundColor = MaterialTheme.colors.primary,
+ backgroundColor = MaterialTheme.colors.primarySurface
)
}
五、处理文本
在处理文本时,我们使用Text
可组合项来显示文本,使用TextFiled
和OutlinedTextField
进行文本输入,并使用TextStyle
对文本应用单一样式。我们可以使用AnnotatedString
对于文本应用多种样式。
正如我们在设置颜色时所看到的那样,用于显示文本的Material组件将获取我们的主题排版自定义设置:
Button(...) {
Text("This text will use MaterialTheme. typography .button style by default")
}
实现此目的要比使用默认参数(如在设置颜色时所看到的那样)略微复杂一些。这是因为组件本身往往不会显示文本,而是提供槽API,让您能够传入Text
可组合项。那么,组件是如何设置主题排版演示的呢?在后台,它们使用ProvideTextStyle
可组合项(本身就使用CompositionLocal
)来设置”current”TextStyle
。如果您未提供具体的testStyle
参数,Text
可组合项会默认查询此“current”样式。
例如,通过Compose的Button
类和Text
类:
@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
) { ...
5.1、主题文本样式
就像处理颜色时一样,最好从当前主题检索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 ovverrides the size in the style
)
在我们的应用中,许多地方都会自动应用主题TextStyle
,例如,TopAppBar
将其title
的样式设为h6
,并将坐着信息和元数据设为body2
:
@Composable
fun Header(...) {
...
Text(
text = text,
+ style = MaterialTheme.typography.subtitle2
)
}
5.2、多种样式
如果您需要对某些文本应用多种样式,可以使用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()} ")
+ }
}
## 六、处理形状
与颜色和排版一样,如果设置形状主题,相应设置会反映在Material组件中。例如,`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
)
) {
6.1、主题形状
当然,在您创建自己的额组件时,您可以自行使用各种形状;为此,您需要使用接受形状的可组合项或Modifier
(例如,Surface
、Modifier.clip
、Modifier.background
、Modifier.border
等)。
@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.shape.small)
)
}
七、组件“样式”
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(horizontao = 16.dp, vertical = 8.dp)
)
}
}
Header
可组合项本质上是样式化的Text
,可供我们在整个应用中使用。
我们都看到了,所有组件都是由较低级别的构建块构造而成的,您可以使用相同的构建块来自定义Material组件。例如,我们看到Button
使用ProvideTextStyle
可组合项为传递给它的内容设置默认文本样式。您可以使用完全相同的机制来设置自己的文本样式:
@Composable
fun LoginButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
color = ButtonConstants.defaultButtonColors(
backgroundColor = MaterialTheme.colors.secondary
),
onClick = onClick,
modifier = modifier
) {
ProvideTextStyle(...) { // set our own text style
content()
}
}
}
在此实例中,我们创建了自己的LoginButton
”样式“,方法是封装标准Button
类,然后指定特定属性(例如不同的backgroundColor
和文本样式)。
此外,也没有默认样式(即自定义某个组件类型的默认外观的方法)的概念。同样,为实现此目的,您可以创建您自己的组件,用于封装和自定义库组件。例如,您想自定义应用中所有Button
的形状,但不想更改小型形状主题,因为更改小型形状主题会影响其他(非Button
组件)。
如需实现此目的,您尅创建自己的可组合项并在整个应用中使用此可组合项:
@Composable
fun AcmeButton(
// expose Button params consumers should be able to change
) {
val acmeButtonShape: Shape = ...
Button(
shape = acmeButtonShape,
// other params
)
}