Android JetPack Compose之主题的理解与使用

news2024/11/25 6:37:09

目录

  • 概述
  • 1.什么是MaterialTheme
  • 2.MaterialTheme与CompositionLocal的联系
    • 2.1 MaterialTheme的工作原理
    • 2.2 CompositionLocal
    • 2.3 CompositionLocal的两种创建方式
      • 2.3.1 compositionLocalOf
      • 2.3.2 staiticCompositionLocalOf
    • 2.4 CompositionLocal总结

概述

根据百度百科知识,主题可以被定义为:文艺作品中所表现的中心思想。我们的APP就是一个作品,所以它的主题也代表了这个APP想要表达的思想,就比如看到京东APP的红色,我们就能明确的知道这是京东系的购物APP,看到淘宝的橙色我们就可以想到是阿里系旗下的产品……,现在的Android手机还有深色主题和亮色主题。分别代表不同的场景。所以主题可以理解为app的整体对外的一个形象,不同风格的主题会展现给人不同的形象。包括颜色,字体,形状等,当然也包括其他类型的多媒体资源,例如文本,声音,和图像等。这些都可以作为主题进行全局配置,当我们想要换风格形象的时候,直接修改全局配置就可以很方便的实现。

1.什么是MaterialTheme

MaterialTheme是Compose所提供的基于Material Design规范的主题样式模板。通过对主题样式模板 配置,整个应用的Composeable组件会随主题的切换实现相应的样式改变。

当我们创建一个新的Compose项目的时候,Android Studio会默认生成一个Theme函数,名称使用的是项目名+Theme的名称 ,比如假设我们创建了一个名为HelloCompose的项目,那么主题的名称就会是:HelloComposeTheme。我们使用Composable组件创建UI都应该是HelloComposeTheme的子元素,这样才能在全局应用主题的效果。如下所示:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            HelloComposeTheme {
                MessageCard(Message("walt","hello Compose"))
            }
        }
    }

    @Composable
    fun MessageCard(msg:Message){
       Row (modifier = Modifier.padding(all=8.dp).background(MaterialTheme.colors.background)){
           Image(painter = painterResource(id = R.drawable.portrait),
               contentDescription = null,
               modifier = Modifier
                   .size(40.dp)
                   .clip(CircleShape)

           )
           Column {
               Text(text = msg.author)
               Spacer(modifier = Modifier.height(4.dp))
               Text(text = msg.body)
           }
       }
    }

    data class Message(val author:String,val body:String)
}

接下来我们可以看下生成的HelloComposeTheme做了哪些事情,先看下主题的定义,如下所示:

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

    MaterialTheme(
        colors = colors, // 颜色
        typography = Typography, // 字体
        shapes = Shapes, // 形状
        content = content // 视图
    )
}

private val DarkColorPalette = darkColors(
    primary = Purple200,
    primaryVariant = Purple700,
    secondary = Teal200,
    background = Color.Gray
)

private val LightColorPalette = lightColors(
    primary = Purple500,
    primaryVariant = Purple700,
    secondary = Teal200,
    background = Color.White
)

从上面代码中我们可以看出,Android Studio默认生成了两种调色板,DarkColorPalette 和 LightColorPalette ,他们分别对应深色主题和亮色主题,根据传入的布尔值参数选择不同的调色板,然后选择的调色板会被透传到MaterialTheme

然后我们继续看darkColors和lightColors两个方法,如下:


fun lightColors(
    primary: Color = Color(0xFF6200EE),
    primaryVariant: Color = Color(0xFF3700B3),
    secondary: Color = Color(0xFF03DAC6),
    secondaryVariant: Color = Color(0xFF018786),
    background: Color = Color.White,
    surface: Color = Color.White,
    error: Color = Color(0xFFB00020),
    onPrimary: Color = Color.White,
    onSecondary: Color = Color.Black,
    onBackground: Color = Color.Black,
    onSurface: Color = Color.Black,
    onError: Color = Color.White
): Colors = Colors(
    primary,
    primaryVariant,
    secondary,
    secondaryVariant,
    background,
    surface,
    error,
    onPrimary,
    onSecondary,
    onBackground,
    onSurface,
    onError,
    true
)

从上面代码中可以看到,lightColors将所有传入的参数全部透传至Colors构造器中,,lightColors帮助我们生成了许多默认的属性值,其中的primary,secondary实际上都是Material Design设计规则规定的主题配色字段。我们可以看出两种调色板的本质只是主题的配色字段不同,我们也可以修改这些配色来满足我们的需求,具体的配色说明如下所示:

Color定义说明
primary整个应用中最常用的主色
primaryVariant主色的变种色,主要用于与主色调做区分的场景。例如APP Bar使用主色,系统状态栏就用变种色
secondary次选色提供了一种用于强调和区分主色的能力,常常用于悬浮按钮,复选框,单选按钮,需要突出的文本,以及链接标题等场景
secondaryVariant次选色的变种,用于与次选色做区分
background背景色,目前主要用作Scaffold系列组件的背景色
surface平面色,常用于平面组件的背景色,例如Surface组件,sheet组件与Menu组件等
error错误色,常用于组件中表示错误的颜色
onPrimary常用于使用primary作为背景色的组件之上的文本与icon的颜色
onSecondary常用于secondary作为背景色的组件之上的文本与icon的颜色
onBackground常用于使用background作为背景色的组件之上的文本与icon颜色
onSurface常用于使用surface作为组件背景色的组件之上的文本与icon的颜色
onError常用于使用error作为背景色的组件之上的文本与icon颜色

下面以一个例子结束本小节

我们要展示一段文字,亮色主题下,字体颜色为红色,暗色主题下字体颜色为蓝色,如下图所示:

亮色主题:
在这里插入图片描述
暗色主题:
在这里插入图片描述

我们实现这个需求很简单,代码如下所示:
先在生成的Theme.kt中定义一个主题:

@Composable
fun CustomColorTheme(
    isDark: Boolean,
    content: @Composable() () -> Unit
) {
    val BLUE = Color(0xFF0000FF)
    val RED = Color(0xFFDC143C)
    val colors = if (isDark) {
        darkColors(primary = BLUE)
    } else {
        lightColors(primary = RED)
    }

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

然后使用的时候添加上我们自定义的主题就行了,代码如下所示:

class TestCustomTheme : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CustomColorTheme(isSystemInDarkTheme()) {
                // A surface container using the 'background' color from the theme
                SampleText("Android")
            }
        }
    }
}

@Composable
fun SampleText(name: String) {
    Text(
        text = "Hello $name!",
        style = MaterialTheme.typography.h4,
        color = MaterialTheme.colors.primary
    )
}

2.MaterialTheme与CompositionLocal的联系

2.1 MaterialTheme的工作原理

接下来,我们通过源码了解下MaterialTheme的工作原理,进入源码如下:

@Composable
fun MaterialTheme(
    colors: Colors = MaterialTheme.colors,
    typography: Typography = MaterialTheme.typography,
    shapes: Shapes = MaterialTheme.shapes,
    content: @Composable () -> Unit
) {
    val rememberedColors = remember {
        // Explicitly creating a new object here so we don't mutate the initial [colors]
        // provided, and overwrite the values set in it.
        colors.copy()
    }.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) {
            PlatformMaterialTheme(content)
        }
    }
}

从上面的代码中我们可以发现,MaterialTheme本身就是一个Composable组件,我们传入的content参数就是声明在Theme中的自定义视图页面组件,透传进ProvideTextStyle然后在其内部进行调用。其中使用了CompositionLocalProvider函数,通过prividers将rememberedColors提供给了LocalColors。

接下来我们继续看下MaterialTheme是如何获取到当前的主题配色的,我们使用的是MaterialTheme.colors.primary。这里可能有人会疑惑,MaterialTheme是一个Composable函数,为啥还能访问其成员属性呢?其实MaterialTheme还有一个同名的单例对象,如下所示:

/**
 * Contains functions to access the current theme values provided at the call site's position in
 * the hierarchy.
 */
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单例对象去获取colors属性时,间接使用的是LocalColors.,这个LocalColors的定义如下:

internal val LocalColors = staticCompositionLocalOf { lightColors() }

通过定义可以知道,它是一个CompositionLocal,初始值是lightColor()返回的Colors配置,。MaterialTheme方法中通过CompoisitionLocalProvider方法为Composable提供了一些CompositionLocal,这其中就包含了所有的主题配置信息。

2.2 CompositionLocal

在很多时候我们需要在Composable视图树中共享一些数据,例如主题配置,一种有效的方式就是通过显示参数传递的方式实现,当参数越来越多的时候,Composable参数列表会变得越来越大并且越来越臃肿,很难维护。当Compose需要彼此间传递数据,并且实现各自的私有性时,如果仍然采用显示参数的方式,则可能会导致意外的崩溃和不可预料的麻烦,为了解决这个问题,Compose提出了CompositionLocal用来完成Composable树中共享数据。CompositionLocal是具有层级的,可以被限定在以某个Composable作为根节点的子树中,默认会向下传递,当然当前子树中的某个Composable可以对该CompositionLocal进行覆盖,从而使得新值会在这个Composable中继续向下传递。

需要注意的是:我们只能在Composable中获取CompositionLocal保存的数据

Compose提供了两种创建CompositionLocal实列的方式,分别是compositionLocalOf与staticCompositionLocalOf.我们简单的以staticCompositionLocalOf方法创建CompositionLocal实例来举个例子帮助了解,代码如下所示:

val localString = staticCompositionLocalOf { "Jetpack Compose!!!" }
@Composable
fun SampleText(name: String) {
   Column{
       CompositionLocalProvider(
           localString provides("walt hello")
       ){
           Text(
               text = "Hello $name!" + localString.current,
               style = MaterialTheme.typography.h4,
               color = MaterialTheme.colors.primary
           )
       }

       CompositionLocalProvider(
           localString provides("XCY hello")
       ){
           Text(
               text = "Hello $name!" + localString.current,
               style = MaterialTheme.typography.h4,
               color = Color.Red
           )
       }
   }
}

运行结果如下:
在这里插入图片描述
从上面的代码和运行结果来看,虽然所有的Composable依赖的均是同一个CompositionLocal,但是其得到的值却是不一样的。我们的主题颜色动态的改变就是根据这个特性设计的。

2.3 CompositionLocal的两种创建方式

2.3.1 compositionLocalOf

在前面我们已经知道创建CompositionLocal实例的方式有两种,分别是compositionLocalOf与staticCompositionLocalOf,那这两种方式有啥区别呢,下面我们将分别介绍
当使用compositionLocalOf来创建CompositionLocal时,如果所提供的值是一个状态,那么当状态发生更新的时候,所有读取这个CompositionLocal内部current数值的Composable都会发生重组。

2.3.2 staiticCompositionLocalOf

如果使用staticCompositionLocalOf创建CompositionLocal实例,那么当状态发生更新时,CompositionLocalProvider的current整体会重组,而不仅仅是在Composable中读取其内部current数值的部分。

2.4 CompositionLocal总结

由此我们可以得出结论:使用compositionLocaOf创建时会记录使用其内部current的所有Composable,当状态发生改变时,对这些Composable进行精准重组。而使用staticcompositionLocaOf创建时会将current整体进行重组。,由于记录Composable是有成本的,所以官方建议如果CompositionLocal提供的值发生变化的可能性很小或者是一个永远不会改变的确定值,那么使用staticCompositionLocalOf可以有效提高性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/765988.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot + Vue 上传Word文档并保留内部格式

因为业务需求&#xff0c;上传Word文件需要编辑&#xff0c;但如何使用Blob方式&#xff0c;在数据库里存文件&#xff0c;就会造成格式消失。所以修改思路&#xff1a;上传文件到服务器本地&#xff0c;保证数据存储的完整性。 前端 <el-upload class"upload-demo&quo…

复习PHP基础教程

PHP 安装 PHP 简介PHP 语法 我需要什么&#xff1f; 如需开始使用 PHP&#xff0c;您可以&#xff1a; 使用支持 PHP 和 MySQL 的 web 主机在您的 PC 上安装 web 服务器&#xff0c;然后安装 PHP 和 MySQL。 使用支持 PHP 的 Web 主机 如果您的服务器支持 PHP&#xff0c…

基数排序|RadixSort|C++实现

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量干货博客汇总https://blog.csdn.net/yu_cblog/c…

电商 api 接口文档

电商 api 接口文档 1、开篇 欢迎使用ShowDoc&#xff01; API格式&#xff1a; 备注&#xff1a;电商API必须返回如下3个字段&#xff1a; 参数名必选类型说明status是int状态message是string信息提示result否mix结果 2、用户相关 2.1、登录/退出 简要描述&#xff1a; …

【复盘】记录一次类型不一致导致的Kafka消费异常问题

背景 业务主要是通过A系统向B系统写入Kafka&#xff0c;然后B系统消费Kafka 将结果写到Kafka中&#xff0c;A进行消费最终结果。 在整个流程中&#xff0c;A写入Kafka会写入一张 record1表记录&#xff0c;然后在A消费最终结果的时候也记录一张record2表。主要改动的话 只是B系…

从Web2到Web3:区块链技术的未来前景

随着互联网的发展&#xff0c;Web1.0、Web2.0 和 Web3.0 成为了人们口中津津乐道的话题。那么&#xff0c;这三种网络时代究竟有什么区别呢&#xff1f; Web1.0 是一个只读的时代&#xff0c;那个时候&#xff0c;用户只能浏览网页&#xff0c;无法进行互动和创作。Web2.0 则是…

什么是社会智商?24种人格力量之社会智商的力量

什么是社会智商&#xff1f; 社会智商指的是将人的智力具体化&#xff0c;自己对他人的了解以及自我剖析能力的高低。一般而言&#xff0c;社会智商越高&#xff0c;对他人的观测能力越高&#xff0c;自我剖析就越透彻。社会智商来源于via 24种人格力量&#xff0c;是人格的优…

FPGA实现UART协议的接收与发送

一、接收模块uart_rx.v UART协议&#xff0c;空闲时&#xff0c;TX和RX数据线都是通过上拉电阻拉高的状态&#xff0c;这样才能在起始位到来时检测到一个下降的边沿。 UART数据格式 uart_rx.v模块输入输出示意图 RX_start。首先&#xff0c;找到起始位的开始时刻RX_start&…

在程序员从业生涯中,哪本书让你醍醐灌顶?

推荐《程序员的README》 [美] 克里斯里科米尼&#xff08;Chris Riccomini&#xff09; 著&#xff0c;付裕 译 每名新入行的工程师在开始工作之前要阅读的书&#xff01;10年大型公司初级工程师指导经验的行业大咖教你如何开启职业生涯、扩展工作技能、应对糟糕管理&#xff0…

信音电子在创业板IPO:募资约9亿元,预计上半年收入约4.3亿元

7月17日&#xff0c;信音电子&#xff08;中国&#xff09;股份有限公司&#xff08;下称“信音电子”&#xff0c;SZ:301329&#xff09;在深圳证券交易所创业板上市。本次上市&#xff0c;信音电子的发行价为21.00元/股&#xff0c;发行数量为为4300万股&#xff0c;募资总额…

Java 压缩多个文件为zip包(中间不生成临时文件,直接压缩为zip二进制流),以及解压zip包二进制流为文件

Java 压缩多个文件为zip包及解压zip包以及压缩多文件为zip文件流解压zip二进制流&#xff08;中间不生成临时文件&#xff0c;直接压缩为zip二进制流&#xff0c;并验证解压&#xff09; 1. 效果图2. 源码 这篇博客将提供俩种方法&#xff0c; 提前生成要压缩的多个文件&#…

vscode debug的方式

在.vscode文件夹下建立launch.json 例子1&#xff1a;调试python 来自 https://github.com/chunleili/tiPBD/tree/amg {"version": "0.2.0","configurations": [{"name": "hpbd 5 5","type": "python&quo…

港联证券|通胀和通缩的区别?通胀对股市有什么影响?

在市场经济上&#xff0c;通货紧缩和通货膨胀是两种比较常见的两种经济现象&#xff0c;那么&#xff0c;通胀和通缩的差异&#xff1f;通胀对股市有什么影响&#xff1f; 港联证证券为大家预备了相关内容&#xff0c;以供参考。 通胀和通缩存在以下差异&#xff1a; 1、定义…

初识操作系统

操作系统 文章目录 操作系统一、上次的问题二、什么是操作系统(Operator System&#xff09;设计操作系统的目的 三、操作系统上下层分别是什么四、先描述&#xff0c;后组织 一、上次的问题 为什么程序运行之前必须先加载到内存&#xff1f; 因为可执行程序&#xff08;文件…

数字化时代,如何做好用户体验与应用性能管理​

引言 随着数字化时代的到来&#xff0c;各个行业的应用系统从传统私有化部署逐渐转向公有云、行业云、微服务&#xff0c;这种变迁给运维部门和应用部门均带来了较大的挑战。基于当前企业 IT 运维均为多部门负责&#xff0c;且使用多种运维工具&#xff0c;因此&#xff0c;当…

【27】SCI易中期刊推荐——计算机科学机器人学(中科院2区)

💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…

arcgis建筑密度容积率覆盖率

大多数研究会把覆盖率当作建筑密度。 用覆盖率更恰当一些。 建筑覆盖率&#xff1a; 建筑物占据面积/街区面积 [Sum_area]/ ([area]*1000000) 排序检查数据&#xff0c;最大值0.75&#xff0c;最小值0. 建筑覆盖率&#xff0c;建筑密度的范围都应该在0-1之内&#xff0c;不是…

linux之Ubuntu系列(三)远程管理指令☞SSH 高级应用 RSA非对称加密 以及免密登录,配置别名

对称加密 、非对称加密 1、对称加密中加密和解密使用的秘钥是同一个&#xff1b;非对称加密中采用两个密钥&#xff0c;一般使用公钥进行加密&#xff0c;私钥进行解密。 2、对称加密解密的速度比较快&#xff0c;非对称加密和解密花费的时间长、速度相对较慢。 3、对称加密的…

【文末送书】AIGC时代的数据分析与可视化

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。搜…

C语言实现:offsetof(OFFSETOF)宏的实现

C语言实现&#xff1a;offsetof宏的实现 offsetof:求结构体成员的偏移量 offsetof:求结构体成员的偏移量 直接上代码&#xff1a; #define OFFSETOF(type,member) ((size_t)(&(((type*)0)->member))) 图解&#xff1a; 图中测试原码&#xff1a; #include<stdi…