使用Jetpack Compose Theme 为 App 轻松换肤

简介: Jetpack Compose Theme

#AndroidDevChallenge Week 3

在这里插入图片描述

整个开发过程中,除了会用到LayoutModifier等基本技术以外,最大的体会就是Compose的Theme太好用了!,这也是Google想在这个题目中考察和传达的重点。虽然不使用Theme也可以完成上面三个页面,但无疑开发效率会大大折扣。

<br/>

2. Compose Theme

传统Android开发中也需要配置Theme,即主题。Theme可以为UI控件提供统一的颜色和样式等,保证App视觉的一致性。主要区别在与:传统Theme依赖xml,而Compose完全基于Kotlin类型更安全、性能更优秀、使用更简单!

Kotlin的优势

当我们在AndroidStudio新建一个Compose模板工程时,IDE会自动创建theme文件夹

在这里插入图片描述

Color.ktShape.ktType.kt中通过Kotlin的常量分别定义各种样式,
Theme.kt中将这些样式应用到全局主题:

//Thmem.kt
private val DarkColorPalette = darkColors(
        primary = purple200,
        primaryVariant = purple700,
        secondary = teal200
)

private val LightColorPalette = lightColors(
        primary = purple500,
        primaryVariant = purple700,
        secondary = teal200
)

@Composable
fun MyAppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
    //根据theme的不同设置不同颜色
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

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

如上,使用Kotlin定义和切换theme都是如此简单,在Composable中基于if语句选择配置,然后静等下次composition生效就好了。

Theme工作原理

每个工程都提供${app name}Theme,用于自定义主题。例如MyAppTheme,最终会调用MaterialTheme,通过一些列Provider将配置映射为环境变量:

@Composable
fun MaterialTheme(
    colors: Colors = MaterialTheme.colors,
    typography: Typography = MaterialTheme.typography,
    shapes: Shapes = MaterialTheme.shapes,
    content: @Composable () -> Unit
) {
    val rememberedColors = remember { colors }.apply { updateColorsFrom(colors) }
    val rippleIndication = rememberRipple()
    val selectionColors = rememberTextSelectionColors(rememberedColors)
    CompositionLocalProvider(
        LocalColors provides rememberedColors,
        LocalContentAlpha provides ContentAlpha.high,
        LocalIndication provides rippleIndication,
        LocalRippleTheme provides MaterialRippleTheme,
        LocalShapes provides shapes,
        LocalTextSelectionColors provides selectionColors,
        LocalTypography provides typography
    ) {
        ProvideTextStyle(value = typography.body1, content = content)
    }
}

后续的UI都创建在MyAppTheme的content中,共享Provider提供的配置

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                // A surface container using the 'background' color from the theme
                ...
            }
        }
    }
}

当需要使用主题配置时,通过MaterialTheme静态对象访问,如下:

@Composable
fun Scaffold(
    ...
    drawerShape: Shape = MaterialTheme.shapes.large,
    ...
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    ...
    backgroundColor: Color = MaterialTheme.colors.background,
    ...
    content: @Composable (PaddingValues) -> Unit
)

MaterialTheme从Provider中获取当前配置。

object MaterialTheme {

    val colors: Colors
        @Composable
        @ReadOnlyComposable
        get() = LocalColors.current

    val typography: Typography
        @Composable
        @ReadOnlyComposable
        get() = LocalTypography.current

    val shapes: Shapes
        @Composable
        @ReadOnlyComposable
        get() = LocalShapes.current
}

<br/>

3. 实战Theme

Bloom是这次挑战赛项目的名字,借助于Compose的Theme,我基本还原了设计稿的要求。

以下是完成效果,代码地址:Bloom

在这里插入图片描述

定义Theme

根据设计稿中的要求,我们在代码中定义Theme:

Color

在这里插入图片描述

首先在Color.kt中定义相关常量

//Color.kt
val pink100 = Color(0xFFFFF1F1)
val pink900 = Color(0xFF3f2c2c)
val gray = Color(0xFF232323)
val white = Color.White
val whit850 = Color.White.copy(alpha = .85f)
val whit150 = Color.White.copy(alpha = .15f)
val green900 = Color(0xFF2d3b2d)
val green300 = Color(0xFFb8c9b8)

然后通过lightColors定义白天的颜色

private val LightColorPalette = lightColors(
    primary = pink100,
    primaryVariant = purple700,
    secondary = pink900,
    background = white,
    surface = whit850,
    onPrimary = gray,
    onSecondary = white,
    onBackground = gray,
    onSurface = gray,
)

其中,primary等的定义来自MaterialDesign设计规范,根据颜色的使用场景频次等进行区分。有兴趣的可以参考MD的设计规范。

onPrimary等表示对应的背景色下的默认前景色,例如text,icon的颜色等:
在这里插入图片描述

相应的,夜间主题定义如下:

在这里插入图片描述

private val DarkColorPalette = darkColors(
    primary = green900,
    primaryVariant = purple700,
    secondary = green300,
    background = gray,
    surface = whit150,
    onPrimary = white,
    onSecondary = gray,
    onBackground = white,
    onSurface = whit850,
)

Type

在这里插入图片描述

//type.kt
val typography = Typography(
    h1 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Bold,
        fontSize = 18.sp,
    ),

    h2 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Bold,
        fontSize = 14.sp,
        letterSpacing = 0.15.sp
    ),

    subtitle1 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Light,
        fontSize = 16.sp
    ),

    body1 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Light,
        fontSize = 11.sp
    ),

    body2 = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.Light,
        fontSize = 12.sp
    ),

    button = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.SemiBold,
        fontSize = 14.sp,
        letterSpacing = 1.sp
    ),

    caption = TextStyle(
        fontFamily = FontFamily.SansSerif,
        fontWeight = FontWeight.SemiBold,
        fontSize = 12.sp
    )

)

Typography定义文字样式。h1body1等也是来自MaterialDesign中对于文字用途的定义。

Shape

在这里插入图片描述

//Shape.kt
val shapes = Shapes(
    small = RoundedCornerShape(4.dp),
    medium = RoundedCornerShape(26.dp),
    large = RoundedCornerShape(0.dp)
)

使用Theme

接下来,在代码中通过MaterialTheme获取当前配置就OK了,无需关心当前究竟是何主题。

在这里插入图片描述

以欢迎页的Beautiful home garden solutionsText为例,文字颜色需要根据主题(Light or Dart)变化。

如下,通过MaterialTheme设置Color可以避免if语句的出现

Text(
    "Beautiful home graden solutions",
    style = MaterialTheme.typography.subtitle1,
    // color = MaterialTheme.colors.onPrimary, //可省略
    modifier = Modifier.align(Alignment.CenterHorizontally),
)

前文介绍过,当背景色为primary时,前景默认会使用onPrimary,所以此处即使不设置Color,也会自动选择最合适的颜色。

再看下面Create accountButton

 Button(
    onClick = {},
    modifier = Modifier
        .height(48.dp)
        .fillMaxWidth()
        .padding(start = 16.dp, end = 16.dp)
        .clip(MaterialTheme.shapes.medium),
        //.background(MaterialTheme.colors.secondary),//Modifier设置背景色
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        )
) {
        Text(
             "Create account",
            // style = MaterialTheme.typography.button // 可省略
        )
}

文字需要以typography.button的样式显示,Button内部的text默认套用button样式,所以此处也可以省略。

Note:需要注意Button有专用的颜色设置字段,使用Modifier设置background无效

由于Button设置了backgroundColorMaterialTheme.colors.secondary,所以,内部的Text的颜色自动应用onSecondary,无需额外指定。

可见,Theme不仅有利于样式的统一配置,还可以节省不少代码量。

<br/>

4. 活用@Preview


视觉的调教有时需要反复确认,如果每次都要安装到设备查看效果将非常耗时。相对于传统xml布局鸡肋的预览效果,Compose提供的@Preview可以达到与真机无异的预览效果,而且还可以同屏预览多个主题、多种分辨率,便于对比。

@Preview(widthDp = 360, heightDp = 640)
@Composable
fun PreviewWelcomeLight() {
    MyTheme(darkTheme = false) {
        Surface(color = MaterialTheme.colors.background) {
            WelcomeScreen(darkTheme = false)
        }
    }
}

@Preview(widthDp = 360, heightDp = 640)
@Composable
fun PreviewWelcomeDark() {
    MyTheme(darkTheme = true) {
        Surface(color = MaterialTheme.colors.background) {
            WelcomeScreen(darkTheme = true)
        }
    }
}

如上,分别对DarkThemeLightTheme进行预览,@Preview中设置分辨率,然后就可以实时看到预览效果了。

在这里插入图片描述

@Preview是通过Composabl的实际运行实现真实的预览效果的,因此预览之前需要build,但是相对于安装到设备查看的方式已经快多了。

基于runtime的preview还有好处就是连交互也可以预览,点击右上角“手指”icon可以与preview进行交互;点击“手机”icon可以将预览画面部署到真机查看。

需要注意的是,因为预览需要保证Composable是可运行的,所以Preview只能接受无参的Composable。对于携带参数的Composable可以通过@PreviewParameter进行mock,但是mock数据本身也有成本,所以我们在设计Composable接口签名时要考虑对Preview是否友好,是否可以减少不必要的参数传递,或者为其提供默认值。

<br/>

5. 最后


最后对Theme的功能以及使用心得做一些总结:

  1. Compose的Theme相对于xml方式更加高效、方便
  2. 合理地使用Theme还有助于减少代码量
  3. 建议在项目开始之前,要求PM或者设计出具详细的Theme定义,提高RD开发效率
  4. 为Composable创建配套的@Preview,将大大提高UI的开发体验

参考

AndroidDevChallenge #Bloom

Theming in Compose

目录
相关文章
|
6月前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
3月前
|
安全 Java Android开发
探索安卓应用开发的新趋势:Kotlin和Jetpack Compose
在安卓应用开发领域,随着技术的不断进步,新的编程语言和框架层出不穷。Kotlin作为一种现代的编程语言,因其简洁性和高效性正逐渐取代Java成为安卓开发的首选语言。同时,Jetpack Compose作为一个新的UI工具包,提供了一种声明式的UI设计方法,使得界面编写更加直观和灵活。本文将深入探讨Kotlin和Jetpack Compose的特点、优势以及如何结合使用它们来构建现代化的安卓应用。
69 4
|
4月前
|
Kubernetes Linux Docker
【Azure 应用服务】使用Docker Compose创建App Service遇见"Linux Version is too long. It cannot be more than 4000 characters"错误
【Azure 应用服务】使用Docker Compose创建App Service遇见"Linux Version is too long. It cannot be more than 4000 characters"错误
|
5月前
|
存储 移动开发 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;
|
6月前
|
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()`。
|
6月前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
6月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
6月前
|
监控 Android开发 数据安全/隐私保护
安卓kotlin JetPack Compose 实现摄像头监控画面变化并录制视频
在这个示例中,开发者正在使用Kotlin和Jetpack Compose构建一个Android应用程序,该程序 能够通过手机后置主摄像头录制视频、检测画面差异、实时预览并将视频上传至FTP服务器的Android应用
|
6月前
深入了解 Jetpack Compose 中的 Modifier
深入了解 Jetpack Compose 中的 Modifier
106 0
|
6月前
|
Android开发
Jetpack Compose: Hello Android
Jetpack Compose: Hello Android