【自学Jetpack Compose 系列】Compose控件(一)Compose的主题的学习与使用

390 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天 点击查看活动详情

Compose中的主题

Compose中的主题同样是使用代码来控制的

1. 主题设置

打开我们之前创建的Compose项目:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyComposeDemoTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    Greeting("Android")
                }
            }
        }
    }
}

代码中使用了主题MyComposeDemoTheme,这个名字是在创建项目的时候根据项目名称自动生成的。下面我们看一下这个MyComposeDemoTheme中都有什么:


private val DarkColorPalette = darkColors(
    primary = Purple200,
    primaryVariant = Purple700,
    secondary = Teal200
)

private val LightColorPalette = lightColors(
    primary = Purple500,
    primaryVariant = Purple700,
    secondary = Teal200

    /* Other default colors to override
    background = Color.White,
    surface = Color.White,
    onPrimary = Color.White,
    onSecondary = Color.Black,
    onBackground = Color.Black,
    onSurface = Color.Black,
    */
)

@Composable
fun MyComposeDemoTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colors = colors,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

很好理解,这里首先定义了两个Colors,分别是深色模式和浅色模式的主题默认颜色。下面的MyComposeDemoTheme是一个可组合函数,它接收两个参数,一个是是否为深色模式,一个是我们的布局。根据是否是深色模式来选择需要的Colors,然后将选好的Colors设置到MaterialTheme中。

我们点击去看看MaterialTheme都有什么?

@Composable
fun MaterialTheme(
    colors: Colors = MaterialTheme.colors,
    typography: Typography = MaterialTheme.typography,
    shapes: Shapes = MaterialTheme.shapes,
    content: @Composable () -> Unit
) {
    ...// 里面的具体实现我们就不看了
}

我们看到MaterialTheme也是一个组合函数,它有4个参数,除了已知的两个参数(colors、content),还有typography和shapes,typography是一组文本样式,shapes是组件要使用的一组形状。

2. 颜色设置

通过上面的主题,我们可以看到,里面的颜色是用的Colors类

@Stable
class Colors(
    primary: Color,
    primaryVariant: Color,
    secondary: Color,
    secondaryVariant: Color,
    background: Color,
    surface: Color,
    error: Color,
    onPrimary: Color,
    onSecondary: Color,
    onBackground: Color,
    onSurface: Color,
    onError: Color,
    isLight: Boolean
){
  ...
var primary by mutableStateOf(primary, structuralEqualityPolicy())
    internal set
var primaryVariant by mutableStateOf(primaryVariant, structuralEqualityPolicy())
    internal set
var secondary by mutableStateOf(secondary, structuralEqualityPolicy())
    internal set
    ...
}

我们传的颜色通过构造方法传入了Colors中。我们看一下Colors类里面的代码,mutableStateOf我们看到了一个熟悉的单词,实在Compose状态时提到过的,这里将Color转为Compose可观察的State。

下面我们在来看看Color:

inline class Color(val value: ULong) {
    @Stable
    val colorSpace: ColorSpace
        get() = ColorSpaces.getColorSpace((value and 0x3fUL).toInt())

    fun convert(colorSpace: ColorSpace): Color 

    @Stable
    val red: Float
    
    @Stable
    val green: Float
        
    @Stable
    val blue: Float
        
    @Stable
    val alpha: Float
        
    companion object {
        @Stable
        val Black = Color(0xFF000000)
        @Stable
        val DarkGray = Color(0xFF444444)
        @Stable
        val Gray = Color(0xFF888888)
        @Stable
        val LightGray = Color(0xFFCCCCCC)
        @Stable
        val White = Color(0xFFFFFFFF)
        @Stable
        val Red = Color(0xFFFF0000)
        @Stable
        val Green = Color(0xFF00FF00)
        @Stable
        val Blue = Color(0xFF0000FF)
        @Stable
        val Yellow = Color(0xFFFFFF00)
        @Stable
        val Cyan = Color(0xFF00FFFF)
        @Stable
        val Magenta = Color(0xFFFF00FF)
        @Stable
        val Transparent = Color(0x00000000)

        @Stable
        val Unspecified = Color(0f, 0f, 0f, 0f, ColorSpaces.Unspecified)
    }
}

代码进行了删减。可以看到,Color类就是一个数据存放类,用于存放颜色值的。

我们要如何自己设置颜色呢?下面是自定义颜色的方法:

// 通过ARGB来设置颜色
@Stable
fun Color(
    red: Float,
    green: Float,
    blue: Float,
    alpha: Float = 1f,
    colorSpace: ColorSpace = ColorSpaces.Srgb
): Color {
    ...
}

@Stable
fun Color(/*@ColorInt*/ color: Int): Color {
    return Color(value = color.toULong() shl 32)
}

@Stable
fun Color(color: Long): Color {
    return Color(value = (color.toULong() and 0xffffffffUL) shl 32)
}

设置主题时,要注意需要给哪些属性设定颜色,Colors中都有默认值,下面记录一下Colors的每个参数对应的颜色分别是什么:

class Colors(
    primary: Color,// 应用程序主要颜色
    primaryVariant: Color,  // 主要变体颜色,用于区分使用主要颜色的应用程序的两个元素
                            // 例如顶部应用程序栏和系统栏
    secondary: Color,   // 辅助颜色,用于浮动操作按钮、选择控件、复选框和单选按钮等
    secondaryVariant: Color, // 辅助变体颜色,用于区分使用辅助颜色的应用的两个元素
    background: Color, // 背景颜色
    surface: Color,  // 表面颜色,用于组件的表面,例如Card、menu
    error: Color,  // 错误颜色,用于指示组件(例如文本字段)中的错误
    onPrimary: Color,  // 用于显示在原色顶部的文本和图标的颜色
    onSecondary: Color, // 用于显示在辅助颜色顶部的文本和图标的颜色
    onBackground: Color, //  用于显示在北京颜色顶部的文本和图标的颜色
    onSurface: Color, // 用于显示在表面颜色顶部的文本和图标的颜色
    onError: Color, // 用于显示在错误颜色顶部的问题和图标的颜色
    isLight: Boolean // 是否为浅色模式
)

有了这些注释之后,是不是就感觉很清晰了。 颜色的使用:

Text(text = "Hello $name!", color = MaterialTheme.colors.primary)

3. 字体设置

我们在设置主题的时候有提到过MaterialTheme中的一个参数:typography。Material定义了一个字体系统,可以使用一些常用的样式:比如字体样式、字体宽度、字号大小等等。那我们要在哪里设置字体呢?

记得我们在说到Compose项目目录的时候,在ui.theme包下面有一个Type.kt文件,字体的配置都可以写在这个文件中:

// Set of Material typography styles to start with
val Typography = Typography(
    body1 = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp
    )
    /* Other default text styles to override
    button = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.W500,
        fontSize = 14.sp
    ),
    caption = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 12.sp
    )
    */
)

接下来我们看看Typography中都有什么吧?

@Immutable
class Typography internal constructor(
    val h1: TextStyle,
    val h2: TextStyle,
    val h3: TextStyle,
    val h4: TextStyle,
    val h5: TextStyle,
    val h6: TextStyle,
    val subtitle1: TextStyle,
    val subtitle2: TextStyle,
    val body1: TextStyle,
    val body2: TextStyle,
    val button: TextStyle,
    val caption: TextStyle,
    val overline: TextStyle
)

可以看到,这里面提到了很多字体,我们在Type.kt文件中就重写了body1、button和caption的字体。我们看到他们的返回类型是TextStyle,但是TextStyle中又有什么呢?

@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, // 字体提供的高级字体设置
                                             // 格式与CSS font-feature-settings属性相同
    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 // 该段的缩进
) 

通过上面的注释,我们对TextStyle就有了一个更深的了解了。 字体的使用:

Text(text = "Hello $name!", color = MaterialTheme.colors.primary, 
style = MaterialTheme.typography.body1)

如果希望应用程序只使用同一种字体时,我们需要指定defaultFontFamily参数, 并省略所有TextStyle元素的fontFamily:

val Typography = Typography(defaultFontFamily = FontFamily.Default)
MaterialTheme(
    ...
    typography = Typography,
    ...
)

4. 形状设置

Compose主题中提到的参数就剩下最后一个了---Shape。在Compose中形状也可以用代码来设置了,我们打开ui.theme包下面的Shape.kt文件:

val Shapes = Shapes(
    small = RoundedCornerShape(4.dp),
    medium = RoundedCornerShape(4.dp),
    large = RoundedCornerShape(0.dp)
)

这是我们创建项目的时候,系统默认帮我们创建的代码。

看着眼熟吧,感觉和Colors一样,那我们先到Shapes类中去看看:

@Immutable
class Shapes(
   // Button或Snackbar之类的小组件使用的形状
    val small: CornerBasedShape = RoundedCornerShape(4.dp),
    // Card 或 AlertDialog 等 中等组件使用的形状
    val medium: CornerBasedShape = RoundedCornerShape(4.dp),
    // 大型组件(例如 ModalDrawer或ModalBottomSheetLayout)使用的形状
    val large: CornerBasedShape = RoundedCornerShape(0.dp)
)

用法与前面的颜色和字体一样

Surface(
    modifier = Modifier.fillMaxSize(),
    color = MaterialTheme.colors.background,
    shape = MaterialTheme.shapes.medium
)

我们发现,不管颜色、字体还是形状都是用MaterialTheme去调用的,下面我们看一下MaterialTheme的源码:

object MaterialTheme {
    /**
     * Retrieves the current [Colors] at the call site's position in the hierarchy.
     *
     * @sample androidx.compose.material.samples.ThemeColorSample
     */
    val colors: Colors
        @Composable
        @ReadOnlyComposable
        get() = LocalColors.current

    /**
     * Retrieves the current [Typography] at the call site's position in the hierarchy.
     *
     * @sample androidx.compose.material.samples.ThemeTextStyleSample
     */
    val typography: Typography
        @Composable
        @ReadOnlyComposable
        get() = LocalTypography.current

    /**
     * Retrieves the current [Shapes] at the call site's position in the hierarchy.
     */
    val shapes: Shapes
        @Composable
        @ReadOnlyComposable
        get() = LocalShapes.current
}

总结

主题的所有属性设置完之后,会保存在MaterialTheme这个单例中,所以我们使用的时候直接通过MaterialTheme调用即可。

后续我们会正式进入Compose控件的学习与实践了。期待中(#^.^#)

最后

感谢你看到最后, 最后再说两点~
①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
我是沐小枫,一个热爱学习、热爱编程的山东人

(文章内容仅供学习参考, 如有侵权,非常抱歉,请立即联系作者删除。)