Jetpack Compose -> 分包 自定义Composable

前言

 上一章我们讲解了 Compose 基础UI 和 Modifier 关键字,本章主要讲解 Compose 分包以及自定义 Composable;

Compose 如何分包

我们在使用 Button 控件的时候,发现如果我们想给按钮设置文本的时候,Button 函数并没有直接提供设置 text 的参数,要我们自己去调用 Text 进行设置;

Column {    
    Button(onClick = {}) {        
        Text(text = "我是老A")    
    }
}

可能到这里的时候,大家就会困惑了,Compose 为什么要这么搞呢?我们可以去源码中一探究竟,我们可以看到 Button 函数是在 androidx.compose.material3 这个包下面

Button 来自 compose.material3 这个组下面的,也就是 Maven 包的 groupId 是 androidx.compose.material3,对应的就是 build.gradle 中的依赖关系

其实Compose 由 androidx 中的 7 个 Maven 组 ID 构成。每个组都包含一套特定用途的功能,并各有专属的版本说明;

Compose 其实一共是分了6层,material 和 material3 是一个,只是不同的分支;每个组下面有不同的分包,我们其实可以看到 ui 下面就有不同的ui、ui-tooling-preview、ui-graphics 等等,Android 团队这么分包,其实是针对 View 系统的一个优化;

View 系统是没有这个分层的,这就导致后期越来越严重的扩展性问题,例如 View 系统中的 ListView,ListView 中有一个对 View 的回收复用机制,这个机制 RecyclerView 是没有办法复用的,也就是它们两个各自维护着一套复用机制,这就是分层不明确导致的;

所以 Compose 在设计之初就明确了分层概念,分层之后的各自扩展,就不会受到限制;

compose.compiler 严格来说,它其实并不属于这7层,它提供的并不是库依赖,它代表的是 kotlin 编译插件,转化 @Composable functions 并启用优化功能,它是负责编译过程的,我们在依赖里面也完全不需要去配置它,只需要在 Compose 的专用配置地方去写上你要的编译插件版本就行,对应的就是这里:

Compose 剩下的 Group 都是我们开发 Compose 的时候会用到的,不过它们有依次递进的依赖关系;

最下层是 compose.runtime 它包含了 Compose 编程模型和状态管理的基本构件块,以及 Compose 编译器插件的目标核心运行时,是最底层的概念模型,比如用来保存状态的 State 就在 compose.runtime,还有 mutableStateOf、remember 

往上一层是 compose.ui 它是用来提供 ui 最基础的功能,比如绘制、测量、布局、触摸反馈等最底层的支持,比如我们使用的所有控件函数,最终都会调用到一个叫 Layout 的函数,这个函数就在 ui 这层;

再往上一层是 compose.animation,它是用来构建动画的;

在往上一层是 compose.foundation,它提供的是一套相对完整可靠的 UI 体系,例如 Colum、Row、Image 等都在这一层;

再往上一层就是 comose.material/material3 了,这是一个封装了 一堆 material design 风格控件的包,如果不想使用 MD 风格,可以使用 foundation 层自己组装一套风格出来;

接下来就是同一个组下面的多个包应该如何引用?例如 compose.ui 下的

implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")

一般来说,我们只需要引入和组名相同的包就可以了,因为一般这个包就包含了这个组下其他包的所有依赖,除了测试组的这种,例如 compose.ui:ui 下不会包含 compose.ui:ui-test-xxx 和 compose.ui:ui-tooling 因为 test  和 工具类的一般都不会编译进我们的 apk 中;

例如 @Preview 就属于 ui-tooling 下的

@Preview
@Composable
fun preview() {    
    Column {        
        Button(onClick = {}) {            
            Text(text = "我是老A")        
        }        
        OutlinedButton(onClick = { /*TODO*/ }) {            
            Text(text = "我是老A")        
        }        
        TextButton(onClick = { /*TODO*/ }) {            
            Text(text = "我是老A")        
        }    
    }
}

还有 material3 提供的一些矢量图组件

implementation("androidx.compose.material3:material3-icon-extends")
implementation("androidx.compose.material3:material3-icon-core")

也是需要单独依赖的;

compose.ui:ui 一般包含了 ui 下的所有, compose.material3:material3 一般包含了 material 下的所有;

自定义Composable

用自定义函数的方式来写 Composable,而 Composable 是一种简化的方式,它指的是带有这个 Composable 注解的函数,那么这个注解到底是做什么的呢?我们来一探究竟

我们在使用的 Text 函数、Image 函数等其实都带有 Composable 注解,但是这些函数并不是原封不动的被调用的,而是会在编译过程中被动了手脚,给它们增加了一些函数参数,然后在运行的时候,调用的其实是那些被改过的参数更多的版本,比如说它们被加入的其中一个参数就是 Composer 类型的,总之这些 Composable 函数在编译的时候会被 Compose 的编译器插件(Compiler Plugin)修改,添加一些参数,运行的时候也是调用的这些被修改过的函数;

那么,编译器为什么要修改它们呢?

最重要的一点就是:要在代码中增加一些我们没有写出来的功能,这些功能对于开发者来说不需要,只需要在程序运行的时候能用到就可以了,所以编译的时候添加,即方便了开发者,又不影响程序的运行;

这其实也是一种面向切面(AOP)编程的思想;

那么编译器插件又是怎么认出这些函数的呢?它怎么直到哪些应该被修改呢?

靠的就是 @Composable 注解;只有被加了这个注解的才会进行修改,起到了识别符的作用;我们可以来看一个小例子:

如果 ui 函数没有添加 @Composable 注解,编译器直接报错了,就是因为这个函数内部调用了被 @Composable 注解的函数,所以我们可以理解为:所有调用了被 @Composable 注解的函数的函数,也必须添加上 @Composable 注解;说到这里的时候,可能会有人有疑问了,setContent 函数添加了 @Composable 注解了吗?如果没有添加,那么它内部怎么可以调用 Compose 函数?如果添加了,那么 MainActivity 为什么不用添加 @Composable 注解?我们来看看 setContent 的实现:

public fun ComponentActivity.setContent(    
    parent: CompositionContext? = null,    
    content: @Composable () -> Unit) {

}

我们发现,setContent 函数并没有被 @Composable 注解标记,它只是把一个 @Composable 注解的函数作为了参数,所以 setContent 不需要被其注解;但是终归还是需要一个被 @Composeable 注解的函数来调用这个参数,那么这个函数是哪个函数呢?它就是 invokeComposable 函数

默认看不了,我们 Decompile to Java 看下

就是将 composable 强转成了一个 Function2 函数,然后进行调用;

所以自定义 Composable 就是声明的函数被 Composable 注解标记,本质上就是为了方便我们在开发中可以将我们的界面元素进行拆分,从而实现不同的功能;通常我们在自定义 Composable 的时候,直接的只会调用一个 Composable 函数,这样方便我们对于布局的控制

@Composable
fun ui() {
    Column {
        Text("老A")
        Text("Mars")
    }
}

而不是

@Composable
fun ui1() {
    Text("老A")
    Text("Mars")
}

那么外部在调用 ui1 函数的时候,我们的布局就不受控制了,如果外部调用的时候 放到了 Column 中,那么就会竖向排列,如果放到了 Row 中,就会横向排列,如果放到了 Box 中就会叠加排列;

而 ui 函数我们可以自己控制布局的排列,通过 Column、Row 等函数,而不用受外界调用控制;

自定义 Composable 的应用场景

再说使用场景的时候,我们可以先想领一个问题,自定义 Composable 在传统 View 中的等价物是什么?自定义View?还是 xml 文件?还是 自定义View + xml 文件?

自定义View?

@Composable    
fun ui() {
    Column {            
        Text(text = "老A")            
        Text(text = "Mars")
    }
}

这种写法,看起来更像传统的 自定义 LinearLayout

class CustomLinearLayout(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {        
    val name: TextView by lazy { TextView(context) }        
    val alias: TextView by lazy { TextView(context) }        
    init {        
        orientation = VERTICAL        
        //
        name.text = "老A"
        alias.text = "Mars" 
        ...        
        // 省略部分代码              
        addView(name)        
        addView(alias)    
    }
}

看起来更像是 自定义 View 的等价物;

xml文件?

但是,这种简易布局我们一般也不会这样去使用,通常都是直接在 xml 中进行了声明

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    android:orientation="vertical"    
    android:layout_width="match_parent"    
    android:layout_height="match_parent">     
    <TextView         
        android:layout_width="wrap_content"         
        android:layout_height="wrap_content"/>        
    
    <TextView        
        android:layout_width="wrap_content"        
        android:layout_height="wrap_content"/>    
</LinearLayout>

这样更直观,便捷,看起来也更像 compose 的写法,一个父控件,两个子控件;

自定义View + xml?

但是如果我们对 Composable 函数做如下改动使用:

@Composable
fun ui(name: String) {    
    Column {        
        Text(text = name)        
        Text(text = "Mars")
    }
}

我们设置了一个 name 作为参数来传入进来,那么我们就可以在调用的时候传入不同的值,来表现不同的数据,而且,这个 Composable 函数还可以这么改

@Composable
fun ui(name: String) {    
    Column {        
        val realName = remember {            
            if (name.length > 8) {                
                "我是laoA"            
            } else {                
                "我是马尔斯"            
            }        
        }        
        Text(text = realName)        
        Text(text = "Mars")
    }
}

对于 Compose 可以这么写,但是对于传统的 xml 实现不了,一旦我们对界面有了定制的需求后,就只能通过自定义 View 来实现了;

所以,看起来自定义 Composable 更像传统 View 的自定义 View + xml 文件!

所以自定义 Composable 的使用场景也就能知道了;

界面声明我们一般是一个 Activity 对应一个 xml 的文件,那么当我们使用 Compose 的时候,也可以一个 MainActivity 对应一个 MainLayout 的 Composable 的函数;

当我们既需要 xml 的简洁有需要自定义view的逻辑处理能力,那么都是可以使用自定义 Composable 的;遇到任务需要对界面有定制需求,就直接使用 Composable 函数处理;

传统自定义 View 还能对布局、绘制、触摸反馈进行定制,这一类的高级自定义 View 在 Compose 中是怎么实现的呢?

其实还是用的自定义 Composable,当然如果你不自定义 Composable,直接硬写也是可以的,但是就失去了扩展、复用的能力,具体写法上,大部分用的是 Modifier,后面章节会详解自定义 Compose 中的高级自定义 View;

好了,自定义 Composable 就讲到这里吧~~

下一章预告

MutableState 和 mutableStateOf 详解;

欢迎三连

来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

读书笔记-《数据结构与算法》-摘要8[桶排序]

桶排序和归并排序有那么点点类似&#xff0c;也使用了归并的思想。大致步骤如下&#xff1a; 设置一个定量的数组当作空桶。Divide - 从待排序数组中取出元素&#xff0c;将元素按照一定的规则塞进对应的桶子去。对每个非空桶进行排序&#xff0c;通常可在塞元素入桶时进行插入…

C#中ArrayList运行机制及其涉及的装箱拆箱

C#中ArrayList运行机制及其涉及的装箱拆箱 1.1 基本用法1.1.1 属性1.1.2 方法 1.2 内部实现1.3 装箱1.4 拆箱1.5 object对象的相等性比较1.6 总结1.7 其他简单结构类 1.1 基本用法 命名空间&#xff1a; using System.Collections; 1.1.1 属性 Capacity&#xff1a;获取或设…

【代码随想录10】20. 有效的括号 1047. 删除字符串中的所有相邻重复项 150. 逆波兰表达式求值

目录 20. 有效的括号题目描述参考代码 1047. 删除字符串中的所有相邻重复项题目描述参考代码 150. 逆波兰表达式求值题目描述参考代码 20. 有效的括号 题目描述 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;…

语音模块学习——LSYT201B模组(深圳雷龙科技)

目录 引子 处理器 外设 音频 蓝牙 模组展示 引子 关注我的老粉们应该知道我之前用过语音模块做东西&#xff0c;那个比较贵要50多。 今天这个淘宝20元左右比那个便宜&#xff0c;之前那个内核是51的&#xff0c;一个8位机。 后面我做东西的时候语音模块可能会换成这个&…

【51单片机】动态数码管

0、前言 参考&#xff1a; 普中51单片机开发攻略–A2.pdf 1、数码管介绍 上一章我们主要是介绍一位数码管的内部结构及控制原理。下面我们再来介 绍下多位数码管及动态显示原理的相关知识。 1.1 多位数码管简介 2、74HC245 和 74HC138 芯片介绍 2.1 74HC245 芯片简介 2.2 7…

Flink入门教程

使用flink时需要提前准备好scala环境 一、创建maven项目 二、添加pom依赖 <properties><scala.version>2.11.12</scala.version></properties><dependency><groupId>org.scala-lang</groupId><artifactId>scala-library<…

探索指针的奇妙世界,程序中的魔法箭头(上)

目录 一.指针是什么二.指针和指针类型1.指针加减整数2.指针的解引用 三.野指针1.野指针形成的原因&#xff08;1&#xff09;指针未初始化指针越界访问 2.如何规避野指针&#xff08;1&#xff09;指针初始化&#xff08;2&#xff09;小心指针越界&#xff08;3&#xff09;指…

用Python实现Excel中的Vlookup功能

目录 一、引言 二、准备工作 三、实现Vlookup功能 1、导入pandas库 2、准备数据 3、实现Vlookup功能 4、处理结果 5、保存结果 四、完整代码示例 五、注意事项 六、总结 一、引言 在Excel中&#xff0c;Vlookup是一个非常实用的函数&#xff0c;它可以帮助我们在表…

014-信息打点-JS架构框架识别泄漏提取API接口枚举FUZZ爬虫插件项目

014-信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&插件项目 #知识点&#xff1a; 1、JS前端架构-识别&分析 2、JS前端架构-开发框架分析 3、JS前端架构-打包器分析 4、JS前端架构-提取&FUZZ 解决&#xff1a; 1、如何从表现中的JS提取…

1.11马原

同一性是事物存在和发展的前提&#xff0c;一方的发展以另一方的发展为条件 同一性使矛盾双方相互吸收有利于自身的因素&#xff0c;在相互作用中各自得到发展 是事物发展根本规律&#xff0c;唯物辩证法的实质和核心 揭示了事物普遍联系的根本内容和变化发展的内在动力 是贯…

VIM工程的编译 / VI的快捷键记录

文章目录 VIM工程的编译 / VI的快捷键记录概述笔记工程的编译工程的编译 - 命令行vim工程的编译 - GUI版vim备注VIM的帮助文件位置VIM官方教程vim 常用快捷键启动vi时, 指定要编辑哪个文件正常模式光标的移动退出不保存 退出保存只保存不退出另存到指定文件移动到行首移动到行尾…

Java面试汇总——jvm篇

目录 JVM的组成&#xff1a; 1、JVM 概述(⭐⭐⭐⭐) 1.1 JVM是什么&#xff1f; 1.2 JVM由哪些部分组成&#xff0c;运行流程是什么&#xff1f; 2、什么是程序计数器&#xff1f;(⭐⭐⭐⭐) 3、介绍一下Java的堆(⭐⭐⭐⭐) 4、虚拟机栈(⭐⭐⭐⭐) 4.1 什么是虚拟机栈&…

【开源】基于JAVA语言的软件学院思政案例库系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统管理员2.2 普通教师 三、系统展示四、核心代码4.1 查询思政案例4.2 审核思政案例4.3 查询思政课程4.4 思政案例点赞4.5 新增思政案例评语 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的软件学…

2024最新:optee系统开发精讲 - 课程介绍

&#xff08;本课程中如有涉及代码或硬件架构&#xff0c;则对应的版本号&#xff1a;TF-A 2.80&#xff0c;optee 3.20, Linux Kernel 6.3&#xff0c;armv8.79.0的aarch64&#xff09; &#xff08;注意&#xff1a; 该课程没有PPT&#xff0c;该课程是对照代码讲解的&#x…

六、Netty核心模块组件

目录 6.1 BootStrap&#xff0c;ServerBootStrap6.2 Future&#xff0c;ChannelFuture6.3 Channel6.4 Selector6.5 ChannelHandler 以及其实现类6.6 Pipeline 和 ChannelPipeline6.7 ChannelHandlerContext6.8 ChannelOption6.9 EventLoopGroup和其实现类 NioEventLoopGroup6.1…

力扣 第 122 场双周赛 解题报告 | 珂学家 | 脑筋急转弯 + 滑窗反悔堆

前言 整体评价 倒开差点崩盘&#xff0c;T4这个反悔堆写吐了&#xff0c;T3往众数上去猜了&#xff0c;幸好case良心。 T1. 将数组分成最小总代价的子数组 I 思路: 取 nums[1:] 的最小2个值 可以部分排序&#xff0c;这样更快捷 class Solution {public int minimumCost(in…

WorkPlus:构建高效协作的企业即时通讯解决方案

在现代企业中&#xff0c;高效沟通是实现协作和改善工作效率的关键。而企业即时通讯工具成为了推进沟通的利器。作为一款高质量的企业即时通讯解决方案&#xff0c;WorkPlus以其卓越的性能和独特的功能&#xff0c;助力企业构建高效协作的新格局。 为什么选择WorkPlus作为企业即…

一文读懂「RAG,Retrieval-Augmented Generation」检索增强生成

Retrieval-Augmented Generation&#xff08;RAG&#xff09;作为机器学习和自然语言处理领域的一大创新&#xff0c;不仅代表了技术的进步&#xff0c;更在实际应用中展示了其惊人的潜力。 RAG结合了检索&#xff08;Retrieval&#xff09;和生成&#xff08;Generation&#…

windows Server 退域操作

要将运行Windows Server 2003的域控制器从Active Directory环境中退出&#xff08;降级&#xff09;&#xff0c;您需要按照以下步骤操作&#xff1a; ### 步骤1&#xff1a;转移角色与功能 - 如果这台服务器拥有任何活动目录角色&#xff0c;如FSMO&#xff08; Flexible Sin…

AI教我学编程之C#类的实例化与访问修饰符

前言 在这篇文章中&#xff0c;我将带大家深入了解C#编程语言的核心概念&#xff0c;包括类的实例化、访问修饰符的应用&#xff0c;以及C#中不同数据类型的默认值。我会通过逐步分析和具体实例&#xff0c;详细解释如何在C#中正确创建和操作对象&#xff0c;并探讨如何通过访…