Jetpack-Compose 学习笔记(六)——Compose 主题 Theme 一探究竟,换肤还能如此 Easy & Silky?(上)

简介: Jetpack-Compose 学习笔记(六)——Compose 主题 Theme 一探究竟,换肤还能如此 Easy & Silky?(上)

断更一时爽,一直断更一直爽~  哈哈哈,就当给自己放了个长假吧。最近的行情太糟了,身边有同学已经被毕业,两个多月终于降薪找到下家··· 这里呼吁大家一定要存好六个月没有工作还能正常生活的银子,以备不时之需!希望疫情能早日平息,经济可以快速恢复吧~


自己也没想到这个系列可以到第六篇,断更确实很久了,居然还收到了小伙伴的催更,感谢你们的不离不弃。闲话少说,我们这次要介绍的是 Compose 主题,那么 Compose 主题 Theme 到底有什么?用 Compose 实现换肤简单吗?一起来看看吧!

Jetpack Compose 的主题 Theme 就是一套 UI 风格,其中包括字体、字号、色值等等,类比于 Android View 体系中的 Theme.MaterialComponents.DayNight.DarkActionBar等等的主题样式。与 View 体系最大的不同在于,它完全抛弃了 xml 文件的设置,所有样式都是通过代码设置的,主题样式大体可以分为 色值、文案样式、形状样式 三大类。先来看看主题中的色值。


1. Color 色值


许多组件不仅支持设置它自己的背景色,还可以设置它包含的其他可组合项的默认色值,使用 contentColorFor方法就可以实现。例如下面 code 1:

// code 1
Surface (color = Color.Yellow,contentColor = Color.Red) {
    Text(text = "July 2021",style = typography.body2)
}

你会发现,Surface的背景色为黄色,而 Text中文案为 红色,如果将 Text换为 Icon,那么 Icon的色调也会变为红色,感兴趣的同学可以试试。

类似 Surface的还有 TopAppBar可组合项,下面是它们的实现源码:

// code 2
Surface(
  color: Color = MaterialTheme.colors.surface,
  contentColor: Color = contentColorFor(color),
  ...
TopAppBar(
  backgroundColor: Color = MaterialTheme.colors.primarySurface,
  contentColor: Color = contentColorFor(backgroundColor),
  ...

Compose 官方推荐使用 Surface来给任何可组合项设置颜色,因为它会设置适当的内容颜色 CompositionLocal值,看 code 2 中 Surfacecolor属性就默认设置了 MaterialTheme.colors.surface色值。不推荐直接调用 Modifier.background设置颜色,因为它并没有设置任何的默认色值。在实际开发中,其实咱也没咋用到 MaterialTheme,所以这里还是看个人吧~

// code 3
-Row(Modifier.background(MaterialTheme.colors.primary)) {    // 不推荐
+Surface(color = MaterialTheme.colors.primary) {    // 推荐
+  Row(
...

在可组合项中,一些 UI 的参数是有默认值的,比如 Alpha 透明度、ContentColor 内容色等。我们可以使用CompositionLocalProvider类去自定义这些属性的默认值。比如:

// code 4
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
    Text(text = "Hello, 修之竹~")
}

对比没有加 CompositionLocalProvider的情况,会发现文案颜色更浅。这是因为,默认情况下 Text文案的 alpha值为 ContentAlpha.high,这里设置为 ContentAlpha.disabled,还有一个 ContentAlpha.mediumalpha值的大小排序为:high > medium > disabled。具体的值可以查看源码,它还分了高对比度和低对比度两种情况。

Compose 在暗夜模式支持方面也做的不错。比如,是否在浅色模式中运行的判断很简单:

// code 5
val isLightTheme = MaterialTheme.colors.isLight

此外,如果在实际中就是使用的 MaterialTheme中的色值来设置,那么需要注意的是,Compose 默认的可组合项中常见的情况是在浅色模式中将容器设为 primary色值,在暗夜模式中将其设为 surface色值,许多组件默认都是使用这种模式,例如TopAppBar(应用栏) 和 BottomNavigation(底部导航栏)。


2. 文案样式


文案样式也可以复用 MaterialTheme中已有的字体样式,当然也可以先将已有的样式 copy 一份,然后修改其中的某些属性。比如可以修改字间距:

// code 6
    Text(
        text = "Hello, 修之竹~",
        // style = MaterialTheme.typography.body1    // 复用 MaterialTheme 中的字体样式
        style = MaterialTheme.typography.body1.copy(    // copy 已有样式并修改字间距属性的值
             letterSpacing = 5.sp
        ),
        fontSize = 20.sp    // 在Text中设置 fontSize 可重写覆盖 MaterialTheme.typography.body1 TextStyle 中的字体大小
    )


2.1 AnnotatedString 类来设置多种样式


AnnotatedString用来代替 SpannableString最好不过了,因为它真的比 SpannableString好用多了!再也不用担心使用  SpannableString引发的数组越界问题了。代码及效果如下,当然还可以实现许多其他的文案样式,感兴趣的同学可以自行查阅 SpanStyle的官方文档。

// code 7
val annotatedString = buildAnnotatedString {
    withStyle(SpanStyle(color = Color.Red, fontWeight = FontWeight.Bold)) {
        append("Kotlin ")
    }
    append("是世上 ")
    withStyle(SpanStyle(fontSize = 24.sp)) {
        append("最好的语言")
    }
}
Text(text = annotatedString)

SpanStyle是设置文案的样式的,作用于字符单位;而如果要针对文案的行高、对齐方式等进行设置,则需要使用ParagraphStyle,顾名思义它是针对段落样式的。


3. 形状样式


MaterialTheme主题中也有 Shape形状属性,在许多的官方 Composable 组件中都有这个 Shape属性,比如 Button组件的 Shape属性默认值就是 MaterialTheme.shapes.small

// code 8
fun Button(
    ···
    shape: Shape = MaterialTheme.shapes.small,
    ···
) {
}

Shapes.kt提供了 smallmediumlarge3 种不同的属性值,其实都是 RoundedCornerShape的具体实现,只不过圆角的大小不太一样罢了,具体数值可查看源码。

如果需要在自定义 Composable 组件中使用 Shape,有两种方法:一是使用拥有 Shape属性的官方 Composable 组件;二是使用 Modifier中可设置 shape的方法去接收自定义 Composable 组件传进来的 Shape参数值。先来看看第一种方法,如 code 9 所示。

// code 9
@Composable
fun RoundedCornerImage(painter: Painter, cornerSize: Int) {
    Surface(
        shape = RoundedCornerShape(cornerSize.dp)
    ) {
        Image(
            painter = painter,
            contentDescription = "圆角图片"
        )
    }
}

这是个可以设置图片圆角大小的自定义 Composable 组件,因为需要用到 Shape设置圆角,所以使用了 Surface这个组件的 Shape  属性来具体实现。

第二种方法就是借助 Modifier的方法,比如 Modifier.clip(shape: Shape)Modifier.background(color: Color, shape: Shape = RectangleShape)Modifier.border(width: Dp, brush: Brush, shape: Shape)等等。比较简单,感兴趣的同学可以试试。


4. 切换主题

上面说了这么多,其实都是针对单个主题说的,在实际应用中,我们可以做个切换主题的小功能,如下图 2 所示:

image.png

其中包含了色值、字体、形状的切换,用到的思路和原理都是一样的,所以这里就只拿主题色值的切换来说明。想要实现这一功能,首先需要明白的是,点击事件之后切换主题的回调该怎么做?

总不能给所有设置色值的地方都设置一个监听器吧?那样做想想都觉得“酸爽”。其实,在 Compose 中,我们可以将当前主题用一个 MutableState对象来保存,然后将主题中的色值集合与这个状态相关联,当用户切换主题改变了这个 MutableState值之后,与之关联的色值集合就会收到回调进行切换,同时通知 Compose 进行重组,这样就使用新的色值集合进行渲染了。

关于 MutableState状态的相关知识,可以查阅我的另一篇文章:Jetpack-Compose 学习笔记(五)—— State 状态是个啥?又是新概念?

OK,整体的思路有了,咱们再详细看看具体是如何实现的。按照之前的分析,我们需要在每次渲染页面的时候读取当前主题的值,所以,首先得先获取当前的主题值。我这里是使用 MMKV存储当前主题值,主题值是 String类型,如下 code 10 所示:

// code 10
    //获取选中的主题 id
    val chosenThemeId = remember {
        mutableStateOf(
            MMKV.defaultMMKV().getString(MMKVConstant.ChosenThemeCode, ThemeKinds.DEFAULT.name)
                ?: ThemeKinds.DEFAULT.name
        )
    }
enum class ThemeKinds {
    DEFAULT,    //默认主题
    RED,    //红色主题
    YELLOW,    //黄色主题
    BLUE    //蓝色主题
}

然后自定义主题,在这里需要规定主题用到的色值、文案样式、形状样式等。在每次切换主题后,在这里还需要根据传入的当前主题值,设置相应的色值组等等。详细如下代码:

// code 11
@Composable
fun CustomTheme(
    chosenThemeId: MutableState<String>,
    content: @Composable () -> Unit
) {
    //自定义主题色值
    val colors = when (chosenThemeId.value) {
        ThemeKinds.DEFAULT.name -> {
            LightColors
        }
        ThemeKinds.RED.name -> {
            RedThemeColors
        }
        ThemeKinds.YELLOW.name -> {
            YellowThemeColors
        }
        ThemeKinds.BLUE.name -> {
            BlueThemeColors
        }
        else -> {
            DarkColors
        }
    }
    MaterialTheme(
        colors = colors,
        typography = typography,
        shapes = shapes
    ) {
        content()
    }
}
//红色主题色值
private val RedThemeColors = lightColors(
    primary = Color(0xFFFF4040),
    background = Color(0x66FF4040)
)
//黄色主题色值
private val YellowThemeColors = lightColors(
    primary = Color(0xFFDAA520),
    background = Color(0x66FFD700)
)
//蓝色主题色值
private val BlueThemeColors = lightColors(
    primary = Color(0xFF436EEE),
    background = Color(0x6600FFFF)
)
private val DarkColors = darkColors(
    primary = Color.White,
    primaryVariant = Red700,
    onPrimary = Color.Black,
    secondary = Red300,
    onSecondary = Color.Black,
    error = Red200
)
private val LightColors = lightColors(
    primary = Color.Black,
    primaryVariant = Red900,
    onPrimary = Color.White,
    secondary = Red700,
    secondaryVariant = Red900,
    onSecondary = Color.White,
    error = Red800,
)

可以看到,在我们自定义的主题 CustomTheme最后,还是使用的 MaterialTheme,只不过将官方的 MaterialThemecolors设置成了我们自己的 colors,同理,我们还可以设置文案 typography和 形状 shapes等参数。

目录
相关文章
|
3月前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
3月前
|
存储 数据库 Android开发
安卓Jetpack Compose+Kotlin,支持从本地添加音频文件到播放列表,支持删除,使用ExoPlayer播放音乐
为了在UI界面添加用于添加和删除本地音乐文件的按钮,以及相关的播放功能,你需要实现以下几个步骤: 1. **集成用户选择本地音乐**:允许用户从设备中选择音乐文件。 2. **创建UI按钮**:在界面中创建添加和删除按钮。 3. **数据库功能**:使用Room数据库来存储音频文件信息。 4. **更新ViewModel**:处理添加、删除和播放音频文件的逻辑。 5. **UI实现**:在UI层支持添加、删除音乐以及播放功能。
|
11天前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
26 4
|
2月前
|
存储 移动开发 Android开发
使用kotlin Jetpack Compose框架开发安卓app, webview中h5如何访问手机存储上传文件
在Kotlin和Jetpack Compose中,集成WebView以支持HTML5页面访问手机存储及上传音频文件涉及关键步骤:1) 添加`READ_EXTERNAL_STORAGE`和`WRITE_EXTERNAL_STORAGE`权限,考虑Android 11的分区存储;2) 配置WebView允许JavaScript和文件访问,启用`javaScriptEnabled`、`allowFileAccess`等设置;3) HTML5页面使用`<input type="file">`让用户选择文件,利用File API;
|
3月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
3月前
|
JavaScript Java Android开发
kotlin安卓在Jetpack Compose 框架下跨组件通讯EventBus
**EventBus** 是一个Android事件总线库,简化组件间通信。要使用它,首先在Gradle中添加依赖`implementation &#39;org.greenrobot:eventbus:3.3.1&#39;`。然后,可选地定义事件类如`MessageEvent`。在活动或Fragment的`onCreate`中注册订阅者,在`onDestroy`中反注册。通过`@Subscribe`注解方法处理事件,如`onMessageEvent`。发送事件使用`EventBus.getDefault().post()`。
|
3月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
3月前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
3月前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
3月前
|
监控 Android开发 数据安全/隐私保护
安卓kotlin JetPack Compose 实现摄像头监控画面变化并录制视频
在这个示例中,开发者正在使用Kotlin和Jetpack Compose构建一个Android应用程序,该程序 能够通过手机后置主摄像头录制视频、检测画面差异、实时预览并将视频上传至FTP服务器的Android应用