Compose知识分享
前言
“Jetpack Compose 是一个适用于 Android 的新式声明性界面工具包。Compose 提供声明性 API,让您可在不以命令方式改变前端视图的情况下呈现应用界面,从而使编写和维护应用界面变得更加容易。”
以上是Compose官网中对于Compose这套全新的Android UI库的介绍,Compose的到来为Android引入了声明式、响应式的全新开发方式,直接以函数调用的形式使用组件,取代了原先冗长的Activity定义以及.xml文件编写;这一点也是当前TDF团队Kuikly框架同样所具备的能力,给予声明式、响应式的特性加速用户开发,并且Kuikly直接复用了Android的view体系,与Compose以全新的Composable以及Compose树而言,一定程度上可以加速新用户对于底层的理解。
Compose执行生命周期
我们继续Compose,Compose在声明式函数的基础上,引入了重组的能力,结合后续的度量策略,Compose UI在发生变化时,会被系统的快照系统SnapManager监测到,并使用协程管道的方式获取信息,然后执行重组动作,重组动作会对状态变更的UI元素进行重新渲染,而不修改状态未改变的其余UI元素。
Compose程序的执行之后,一直到页面渲染出结果以及后续的持续UI变更,共可以分为4个阶段,分别是“组合 --> 布局 --> 渲染 <--> 重组”;
组合阶段
“在组合阶段,Compose 运行时会执行可组合函数,并 输出一个表示界面的树结构。这个界面树由 包含下一阶段所需的所有信息的布局节点”(官方文档)
因此我们不难理解,Compose仍然是采用了树型结构作为UI加载、布局、渲染的底层结构,但是有所变更的是如今的Compose树的节点类型为ComposeUiNode,在组件树真正开始被载入时会变成LayoutNode类型的节点。LayoutNode是ComposeUiNode的子类,其继承了ComposeUiNode中所有组件设置的属性。对应的Row、Image、Column以及Text组件所形成的LayoutNode树显示如下:
因此我们可以明确了第一点,即组合阶段就是产生所有组件的LayoutNode树,为后续布局阶段提供遍历的内容。
结合源码我们梳理了组合阶段的执行流程,显示如下:

相关源码展示如下,setContent中内部的函数调用在此不做展示;可以结合流程图去检索源码即可了解其中的调用链。
private object TutorialSnippet1 {class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Text("Hello world!")}}}}public fun ComponentActivity.setContent(parent: CompositionContext? = null,content: @Composable () -> Unit) {val existingComposeView = window.decorView.findViewById<ViewGroup>(android.R.id.content).getChildAt(0) as? ComposeViewif (existingComposeView != null) with(existingComposeView) {setParentCompositionContext(parent)setContent(content)} else ComposeView(this).apply {// Set content and parent **before** setContentView// to have ComposeView create the composition on attachsetParentCompositionContext(parent)setContent(content)// Set the view tree owners before setting the content view so that the inflation process// and attach listeners will see them already presentsetOwners()setContentView(this, DefaultActivityContentLayoutParams)}}
布局阶段
在布局阶段,Compose 会使用在组合阶段生成的界面树 作为输入,计算每个布局节点在界面树中的测量和放置位置,此过程中将遍历每个LayoutNode节点执行子节点测量、自我测量以及放置子节点三个过程。
以上图的LayoutNode节点树为例,此过程可描述为:
| 1、Row 测量其子项 Image 和 Column。 2、系统测量了 Image。它没有任何子元素,因此它决定自己的 尺寸,并将尺寸报告给 Row。 3、接下来测量 Column。它会测量自己的子项(两个 Text 可组合项)。 4、系统将测量第一个 Text。由于没有任何子元素,因此它决定 自己的尺寸,并将其尺寸报告给 Column。系统测量第二个 Text。由于没有任何子元素,因此它决定 并将其报告给 Column。 5、Column 使用子测量值来确定自己的尺寸。它使用 最大子元素宽度及其子元素高度之和。 6、Column 会相对于自身放置其子项,并将其置于子项下方 相互垂直 7、Row 使用子测量值来确定自己的尺寸。它使用 子元素的最大高度及其子元素宽度之和。然后 子项。 |
采用深度遍历的方式执行LayoutNode树的遍历,基于constrains约束,树上每个组件所在的位置在遍历完成后都将计算完成,即完成布局阶段,等待后续的渲染阶段。
而布局阶段中,Modifier类已经开始在遍历的时候就被加载,并在宽高计算时被认为是待度量的因素。具体各组件的度量策略我附上一张表格。
!!! 目前核心关心的是Modifier属性是怎么被加载的?
① 组成Modifier链
Surface(modifier = Modifier.size(200.dp) // 设置组件大小.background(Color.LightGray) // 设置背景颜色.offset(x = 10.dp, y = 20.dp) // 设置偏移量) {Text(text = "Hello, Jetpack Compose!",fontSize = 20.sp,color = Color.Black,modifier = Modifier.padding(8.dp) // 内部文本的额外内边距)}
以此代码为例,Surface组件使用Modifier伴生对象基于链式调用的方式声明了多个属性,也正如此Modifier是Compose中修饰 UI 组件的关键数据结构。
interface Modifier {fun <R> foldIn(initial: R, operation: (R, Element) -> R): Rfun <R> foldOut(initial: R, operation: (Element, R) -> R): Rfun any(predicate: (Element) -> Boolean): Booleanfun all(predicate: (Element) -> Boolean): Booleaninfix fun then(other: Modifier): Modifier = ...interface Element : Modifier {...}companion object : Modifier {...}}
Modifier 接口有三个直接实现类或接口:伴生对象 Modifier、内部子接口Modifier.Element、CombinedModifier。
- 伴生对象 Modifier:最常用的 Modifier, 即在代码中使用 Modifier.xxx();
- 内部子接口 Modifier.Element:当我们使用Modifier.xxx()时,其内部实际会创建一个Modifier 实例。如使用 Modifier.size(100.dp) 时,内部会创建一个 SizeModifier 实例;
其背后的继承关系是

而类似LayoutModifier的中间类显示如下,不同的中间Modifier.Element子类将使得Modifier链建立时使用CombinedModifier来链接;

② Modifier链的构建过程
- then()
size属性定义时建立的SizeModifier实例被当作参数传入 then() 方法中。而这个 then() 方法就是 Modifier 间相互连接的关键方法。
Modifier.size(100.dp)fun Modifier.size(size: Dp) = this.then( // 关键方法SizeModifier(...))// 返回待连接的Modifiercompanion object : Modifier {...override infix fun then(other: Modifier): Modifier = other}
- 继续链式调用后续属性,BackGround, padding
Modifier.size(100.dp).background(Color.Red)fun Modifier.background(color: Color,shape: Shape = RectangleShape
) = this.then( // 当前 this 指向 SizeModifier 实例Background(...))
- 而此时Modifier链也将引入新的类型CombinedModifier来链接所对应的中间Modifier.Element子类不一致时的Modifier
interface Modifier {infix fun then(other: Modifier): Modifier =if (other === Modifier) this else CombinedModifier(this, other)}class CombinedModifier(private val outer: Modifier,private val inner: Modifier) : Modifier
形成以下的样式

所有属性全部上链会形成

至此Modifier链已经构建完成,而在LayoutNode遍历时会基于nodes属性访问管理Modifier链的Nodechain类;
internal val nodes = NodeChain(this)
而辅助遍历时的数据结构是
internal val innerCoordinator: NodeCoordinatorget() = nodes.innerCoordinatorinternal val layoutDelegate = LayoutNodeLayoutDelegate(this) internal val outerCoordinator: NodeCoordinatorget() = nodes.outerCoordinator
innerCoordinator是指最内层的协调器,直接与LayoutNode关联,负责处理Modifier链的内部操作,包括处理内部Modifier、管理LayoutNode子LayoutNode,协助他们的度量工作以及传递事件;
outerCoordinator是指最外层的协调器,负责处理Modifier链的外部操作,包括处理外部Modifier,协调Modifier的调用顺序以及管理LayoutNode父节点;
其中内部Modifier和外部Modifier的分辨规则是:
- 内部 Modifier 是指那些主要影响组件的布局和测量的修饰符。这些修饰符通常在LayoutNode的内部进行操作,改变组件的尺寸、位置和布局行为。( padding, size, fillMaxWidth / fillMaxHeight, wrapContentSize, aspectRatio, weight)
- 外部 Modifier 是指那些主要影响组件的绘制和偏移的修饰符。这些修饰符通常在LayoutNode的外部进行操作,改变组件的外观、透明度和位置偏移等。( background, border, offset, alpha, clip, shadow);
而其中访问Modifier链的过程将基于foldin与foldout方法取到Modifier上所有的设置信息,从而将外部 or 内部 属性的定义值反馈到当前LayoutNode的width, height上,并最后在父节点的constrains限制下输出最大的width和height;
fun <R> foldIn(initial: R, operation: (R, Element) -> R): R
// foldIn(): 正向遍历 Modifier 链,SizeModifier-> Background -> PaddingModifierfun <R> foldOut(initial: R, operation: (Element, R) -> R): R
// foldOut(): 反向遍历 Modifier 链, PaddingModifier -> Background ->SizeModifier
绘制阶段
绘制代码期间的状态读取会影响绘制阶段。常见示例包括 Canvas()、Modifier.drawBehind 和 Modifier.drawWithContent。
Modifier知识补充
Compose UI组件 Modifier子类 类图

Compose 常用UI组件 LayoutNode、Modifier、MeasurePolicy汇总

相关文章:
Compose知识分享
前言 “Jetpack Compose 是一个适用于 Android 的新式声明性界面工具包。Compose 提供声明性 API,让您可在不以命令方式改变前端视图的情况下呈现应用界面,从而使编写和维护应用界面变得更加容易。” 以上是Compose官网中对于Compose这套全新的Androi…...
python-study-day5
urllib中handler的使用 import urllib.request url "http://www.baidu.com" headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0 } # 请求地址的定制 reques…...
Telegram mini app 本地开发配置
前言: 为了能在telegram里本地调试mini app,参考了网上很多方案,踩了不少坑。最后整了一个适合自己的方案,记录一下。 这个方案一定不是最好的,不过是目前适合我上手开发的方案了。 本文章适合需要在 telegram 本地…...
python发票查验接口助您拒绝做糊涂账、发票ocr
发票识别发票查验接口让发票真假立现。仅需一键上传发票图片,即可实现发票真伪的秒速、批量验证,操作简单方便,避免因人工核验失误所导致“错账”现象的发生,减轻财务工作负担,提升企业工作效率,降低因假票…...
【Linux】线程控制|POSIX线程库|多线程创建|线程终止|等待|线程分离|线程空间布局
目录 编辑 POSIX线程库 多线程创建 独立栈结构 获取线程ID pthread_self 线程终止 return终止线程 pthread_exit pthread_cancel 线程等待 退出码问题 线程分离 测试 线程ID及地址空间布局 编辑 POSIX线程库 pthread线程库是 POSIX线程库的一部分…...
JimuReport 积木报表 v1.8.0 版本发布,开源可视化报表
项目介绍 一款免费的数据可视化报表工具,含报表和大屏设计,像搭建积木一样在线设计报表!功能涵盖,数据报表、打印设计、图表报表、大屏设计等! Web 版报表设计器,类似于excel操作风格,通过拖拽完…...
性能优化理论篇 | swap area是个什么东西
我们知道每台计算机的内存(RAM)都是有限的,而我们的应用程序需要加载到内存才能被运行,如果一台机器运行多个应用程序时,内存可能会耗尽。Linux 系统中的“交换空间(也称为交换分区)”可以帮助缓…...
Photoshop (PS)下载安装win/mac版
目录 一、概述 下载 二、安装步骤 三、使用教程 四、快捷键汇总 一、概述 Adobe Photoshop,简称“PS”,是由Adobe Systems开发和发行的图像处理软件。它主要处理以像素所构成的数字图像,涵盖了诸多领域,如图像编辑、图像合成…...
初识redis:Set类型
Set有很多种含义,比如集合,比如设置(和get相对应)。 在这里我们说的set是指的redis中的集合,并且这里的集合是无序的,和之前的list是对应的。 List : [1,2,3] 和 [2,1,3] 是两个不同的listSe…...
Qt 设置QTableView的某列或某行只读
我的做法是实现一个委托(delegate),然后把它设给你要只读的列或行 class ReadOnlyDelegate: public QItemDelegate {public:ReadOnlyDelegate(QWidget *parent NULL):QItemDelegate(parent){}QWidget *createEditor(QWidget *parent, const…...
这么多焊工证,我该拥有那种焊工证呢?
焊接与切割作为特种作业工作,国家根据不同的环境和作业特点,从事焊接工作的焊工需要持证上岗工作,那么是不是焊接就只有一种工作证呢?如果不是,焊工所在的工作岗位需要什么样的焊工证,如何取得焊工证&#…...
HttpWebRequest访问https请求被中止: 未能创建 SSL/TLS 安全通道
编写代码,使用HttpWebRequest访问网页,错误提示: 未能创建 SSL/TLS 安全通道 页面本来是Http网址,突然转换为https网址,所以遇到这个问题也算正常。将解决方法记录下来给需要的人。 //只需要在 HttpWebRequest 发送…...
响应式Web设计:纯HTML和CSS的实现技巧
文章目录 响应式Web设计:纯HTML和CSS的实现技巧一、响应式Web设计概述二、实现响应式设计的技巧1. 使用媒体查询(Media Queries)2. 使用弹性布局(Flexbox)3. 使用网格布局(CSS Grid)4. 使用相对…...
linux centos 防火墙常用命令
1、开放端口 firewall-cmd --zonepublic --add-port80/tcp --permanent 1 2、查看某端口是否开放 firewall-cmd --query-port80/tcp 1 3、查看端口开启列表 firewall-cmd --list-port 1 4、重启防火墙 firewall-cmd --reload 1 5、关闭防火墙 systemctl stop firewalld.se…...
iOS18 Beta7 最终测试版推送:苹果的又一次技术飞跃
苹果公司以其一贯的创新精神和对完美的追求,再次引领了科技界的新潮流。今天,我们聚焦于苹果最新推送的iOS18 Beta7最终测试版,这一版本不仅是苹果软件更新的里程碑,更是用户体验的一次重大升级。 最终测试版的亮相 在众多果粉的…...
超详细!2024叉车证办理及考试流程详解!速看!
根据《特种设备安全监察条例》规定:叉车操作员必须经专业培训和考核,取得地、市级以上质量技术监督行政部门颁发的作业人员资格证书,方可从事该项工作。叉车驾驶证是属于特种车辆驾驶证,要到全国市级质量监督局指定叉车训练基地报…...
浅谈监听器插件之SSHMon Samples Collector
浅谈监听器插件之SSHMon Samples Collector SSHMon Samples Collector 是 JMeter 的一个强大插件,它允许用户通过 SSH 协议远程监控目标服务器的资源利用率,为性能测试提供实时的系统性能数据。这对于评估应用在高负载条件下的表现以及定位性能瓶颈至关…...
R语言论文插图模板第7期—分组散点图
在之前的文章中,分享过R语言折线图的绘制模板: 柱状图的绘制模板: 本期再来分享一下散点图(分组)的绘制方法。 先来看一下成品效果: 特别提示:本期内容『数据代码』已上传资源群中,…...
设计模式 抽象工厂方法模式
定义与特点 工厂方法模式(FactoryMethod),定义一个创建产品对象的工厂接口,让工厂子类决定实例化那一个产品类。工厂方法使一个类的实例化延迟到其子类。 我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂…...
PowerShell 一键配置IP
前言 实现一键更改Windows 网卡IP,子网,网关,dns,重命名网卡,获取的接口索引名称,获取接口名称,刷新组策略,刷新系统,脚本可重复配置,,以下环境我是两个网卡配置IP 前提条件 开启wmi,配置网卡,参考 创建更改网卡脚本 实验环境,两个网卡,清除默认,重命名(配置)…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
