带你深入解析 Compose 的 Modifier 原理 -- Modifier、CombinedModifier

Modifier 的含义


实际开发过程中,随处可见各种 Modifier,比如:

  Modifier.size()         // 尺寸Modifier.width()        // 宽度Modifier.height()       // 高度Modifier.padding()      // 间距Modifier.background()   // 背景Modifier.clip()         // 裁切Modifier.clickable()    // 点击... ...

这个根部的 Modifier 是个啥?

interface Modifier {// 申明一个 Modifier 的伴生对象(单例对象)companion object : Modifier {// 里面全部都是最简单的实现override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R = initialoverride fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initialoverride fun any(predicate: (Element) -> Boolean): Boolean = falseoverride fun all(predicate: (Element) -> Boolean): Boolean = trueoverride infix fun then(other: Modifier): Modifier = otheroverride fun toString() = "Modifier"}}

所以,如果你单写一个 Modifier,就可以获取到一个最简单的 Modifier 接口的对象(即伴生对象),它的作用就是作为一个起点。


我们现在做一个场景引入,比如自定义一个 Compose 函数:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {custom()}}}
}@Composable
fun custom() {Box(Modifier.size(40.dp).background(Color.Blue)) {}
}

效果如下:

在这里插入图片描述

这个场景很简单,在界面上添加了一个 Box,并且设定了背景色。如果现在我希望外部调用这个 custom 函数的时候可以从外部去设置它的透明度呢?我们可以这么写:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {Custom(modifier = Modifier.alpha(0.5f))}}}
}@Composable
fun Custom(modifier: Modifier) {Box(modifier.size(40.dp).background(Color.Blue)) {}
}

效果如下:

在这里插入图片描述

这样外部就可以通过传入一个 Modifier 去修改方块的尺寸了,但是这就存在一个问题了,外部只要调用 custom() ,就必须传入一个 Modifier,这就不合理了,相当于强制外部要加这个 Modifier 参数,而外部此时并不想修改尺寸,但也希望调用这个 custom 展示一个方块怎么办呢?

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {Custom(modifier = Modifier.alpha(0.5f))  // 传了,custom 就用传入的 ModifierCustom()  // 不传,custom 就用默认的}}}
}@Composable
fun Custom(modifier: Modifier = Modifier) {  // 设定一个默认的 Modifier,作为一个占位符使用Box(modifier.size(40.dp).background(Color.Blue)) {}
}

这是一个很标准的写法。

我们看下 Box 这个 Compose 函数:

@Composable
inline fun Box(modifier: Modifier = Modifier,contentAlignment: Alignment = Alignment.TopStart,propagateMinConstraints: Boolean = false,content: @Composable BoxScope.() -> Unit
) 

换一个 Button 看看:

@Composable
fun Button(onClick: () -> Unit,modifier: Modifier = Modifier,enabled: Boolean = true,... ...
) 

换一个 Column 看看:

@Composable
inline fun Column(modifier: Modifier = Modifier,verticalArrangement: Arrangement.Vertical = Arrangement.Top,horizontalAlignment: Alignment.Horizontal = Alignment.Start,content: @Composable ColumnScope.() -> Unit
) 

Compose 本身提供的函数参数也是这种标准写法,所以以后写自定义的 Compose 函数,我们也按照这种标准写法就会很方便。



Modifier 链


所谓的 Modifier 链 其实就是类似 Modifier.padding().background() 这样的链式调用。在实际开发过程中,这种链式调用对顺序的敏感度还是很强的,不同的调用顺序显示出来的结果会完全不一样。


接下来我们结合实际代码同步分析 Modifier 链的创建步骤。

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {Box(Modifier.background(Color.Blue))}}}
}

对于 Modifier,我们之前说过它相当于一个白板,而 Modifier.background() 是 Modifier 的扩展函数:

fun Modifier.background(color: Color,shape: Shape = RectangleShape
) = this.then(     // this:就是 Modifier,这里又调用了 then()Background(color = color,shape = shape,inspectorInfo = debugInspectorInfo {name = "background"value = colorproperties["color"] = colorproperties["shape"] = shape})
)

此时 this 指针指向的是伴生对象 Modifier,接下来我们看看 then() 做了什么。

then()

@Stable
interface Modifier {// 接口中的方法infix fun then(other: Modifier): Modifier =if (other === Modifier) this else CombinedModifier(this, other)}
  1. 我们可以看到 then() 是有参数的,它的参数也是一个 Modifier 类型的,所以 Background() 也是一个 Modifier?
private class Background constructor(... ...
) : DrawModifier, InspectorValueInfo(inspectorInfo) {

Background() 实际上就是一个 DrawModifier。

  1. then() 的作用:把调用的 Modifier(左边)和参数传入的 Modifier(右边)进行合并的。

if (other === Modifier) this:如果参数是一个最基本的 Modifier 伴生对象:companion object,则返回自己,即调用者。

比如如果我这么写:

Box(Modifier.background(Color.Blue).then(Modifier))
==>
就会直接返回 Box(Modifier.background(Color.Blue) 自身

那我们例子里的 Modifier.background() 返回的是什么呢?

Box(Modifier.background(Color.Blue)
// 它返回的是什么?
// 很明显是 then(Background()) 不满足 other === Modifier 的条件,所以应该走 CombinedModifier?// 请注意:Modifier 是一个伴生对象,它内部覆写了 then() 方法companion object : Modifier {... ...// 传进来什么 Modifier,就返回什么 Modifieroverride infix fun then(other: Modifier): Modifier = other
}

所以 Modifier.background() 这种调用方本身就是一个伴生对象的情况,会走到它自己内部的 then() 方法,返回的是 Background()。

在这里插入图片描述

此时 Modifier 链的数据结构如下:

在这里插入图片描述

如果“调用者”和“传进来的参数”都不是 Modifier 伴生对象的话,就会走到下面一个条件。

else CombinedModifier(this, other):调用 CombinedModifier() 进行两个 Modifier 的融合,并返回自身。

比如我们现在再添加一个 size(40.dp):

Box(Modifier.background(Color.Blue).size(40.dp))

查看 size() 函数:

un Modifier.size(size: Dp) = this.then(SizeModifier(... ...)
)

这个时候的 this 就是 Background() 了(也就是 DrawModifier),而 then() 内部的参数又是一个 SizeModifier,这个时候就要用到 CombinedModifier 进行融合了。

class CombinedModifier(internal val outer: Modifier,internal val inner: Modifier
) : Modifier

此时 Modifier 链的数据结构如下:

在这里插入图片描述

比如我们现在再添加一个 Padding(10.dp):

Box(Modifier.background(Color.Blue).size(40.dp).padding(10.dp))

查看 padding() 函数:

fun Modifier.padding(all: Dp) =this.then(PaddingModifier(... ...))

这个时候的 this 指向的是 CombinedModifier 实例,而 then() 内部的参数又是一个 PaddingModifier,这个时候就又要用到 CombinedModifier 进行融合了。

此时 Modifier 链的数据结构如下:

在这里插入图片描述

所以你会发现,目前我们看到的 Modifier 函数都有一个 then() 作为最外层调用,它 可以作为一个套子,打造一个一环套一环的链条。


接下来我们继续分析,查看 CombinedModifier 函数源码:

class CombinedModifier(private val outer: Modifier,private val inner: Modifier
) : Modifier

CombinedModifier 连接的两个 Modifier 分别存储在 outerinner 中,Compose 对 Modifier 的遍历,就是从外(outer)到内(inner)一层层访问。需要注意的是, outerinnner 字段都被 private 关键字申明,意味着不能被外部直接访问,但官方为我们提供了 foldOut()foldIn() 专门用来遍历 Modifier 链。

class CombinedModifier(private val outer: Modifier,private val inner: Modifier
) : Modifier {override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =inner.foldIn(outer.foldIn(initial, operation), operation)    // 正向遍历override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =outer.foldOut(inner.foldOut(initial, operation), operation)  // 反向遍历override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =outer.any(predicate) || inner.any(predicate)    // 或运算override fun all(predicate: (Modifier.Element) -> Boolean): Boolean =outer.all(predicate) && inner.all(predicate)    // 与运算
}

例如:

Modifier.background(Color.Blue).size(40.dp).padding(10.dp)

foldIn: 正向遍历 Modifier 链,DrawModifier -> SizeModifier -> PaddingModifier

foldOut: 反向遍历 Modifier 链,PaddingModifier -> SizeModifier -> DrawModifier

foldIn() 解析

我们看下 foldIn 源码:

fun <R> foldIn(initial: R, operation: (R, Element) -> R): R

它有两个参数:

  1. initial:初始值,这个初始值未必一定是 Modifier
  2. operation:每遍历到一个 Modifier 时的回调,这个 lambda 又有两个参数,R类型与 Element类型

foldIn 方法在遍历当前 Modifier 时执行的 operation 的 返回值 将作为链中下一个 Modifier 的 operation 的 R 类型 参数传入

foldOut() 解析

foldIn() 相反, 我们就不再单独展开分析。



Modifier.Element


在 Modifier 整个体系里面,有很多 Modifier 接口的子接口和实现类,除了我们之前提过的 companion object 伴生对象和 CombinedModifier 类之外,其他所有的 Modifier,不管是接口还是实现类,全部都直接或者间接的继承了 Modifier 的另外一个子接口:Element


jbVW8S.png


interface Element : Modifier {/*** 给定一个初始对象,再给出一个算法,第一个 Modifier 用到这个初始对象上会有第一个返回结果* 第二个 Modifier 用在第一个返回结果并返回一个结果,然后一层套一层。*/override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R =operation(initial, this)// 与 foldIn 相反override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R =operation(this, initial)// 检查 CombinedModifier 内部所有 Modifier 是否有一个满足条件override fun any(predicate: (Element) -> Boolean): Boolean = predicate(this)// 检查 CombinedModifier 内部所有 Modifier 是否全部都满足条件override fun all(predicate: (Element) -> Boolean): Boolean = predicate(this)}

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

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

相关文章

2023年第四届 “赣网杯” 网络安全大赛 gwb-web3 Write UP【PHP 临时函数名特性 + 绕过trim函数】

一、题目如下&#xff1a; 二、代码解读&#xff1a; 这段代码是一个简单的PHP脚本&#xff0c;它接受通过GET请求传递的两个参数&#xff1a;‘pass’和’func’&#xff1a; ① $password trim($_GET[pass] ?? );&#xff1a;从GET请求中获取名为’pass’的参数&#xff0…

C语言——内存函数的使用与模拟实现

大家好&#xff0c;我是残念&#xff0c;希望在你看完之后&#xff0c;能对你有所帮助&#xff0c;有什么不足请指正&#xff01;共同学习交流 本文由&#xff1a;残念ing 原创CSDN首发&#xff0c;如需要转载请通知 个人主页&#xff1a;残念ing-CSDN博客&#xff0c;欢迎各位…

【小白专用】php pdo sqlsrv 类,php连接sqlserver

1.找到自己版本&#xff0c;我的程序是64位的。 注意&#xff1a;ts与nts的区别&#xff0c;查看phpinfo信息&#xff0c;如下 <?phpecho phpinfo();?> 2.运行后&#xff0c;可以查看到如下数据&#xff1a; ① PHP 的版本是8.2.13&#xff1b; ② 属于线程安全版 ts…

远程多窗口和Screen用法

Termius 远程链接服务器终端时&#xff0c;经常遇到需要开多个窗口&#xff0c;另外还可能涉及到正在运行的程序一旦和服务器链接断开&#xff0c;那么程序也就停止执行了。对于单单只需要多个窗口的问题&#xff0c;建议下载一个Termius这样软件&#xff0c;比多次打开…

西门子S71200系列PLC通过PROFINET连接多功能电表

西门子S71200连接多功能电表 1、需求描述&#xff1a; 通过西门子S7-1200系列PLC&#xff0c;连接多功能电表&#xff0c;通过Modbus协议读写电表的数据。 2、方案描述&#xff1a; 桥接器的网口连接西门子S7-1200系列PLC的网口&#xff0c;串口连接到电表的485通讯口&#x…

2023美团商家信息

2023美团商家电话、地址、经纬度、评分、均价、执照...

第一节TypeScript 安装

一、TypeScript 安装 前提条件&#xff1a;我们环境中已经配置npm环境。 1、使用npm安装TypeScript 首先查看你本地是否已安装npm。打开cmd -> 输入“npm -v” 回车&#xff0c;查看输出的npm版本 上述输出代码你本地环境已经安装了npm工具&#xff0c;可以使用以下命令来…

【数据结构】并查集的简单实现,合并,查找(C++)

文章目录 前言举例&#xff1a; 一、1.构造函数2.查找元素属于哪个集合FindRoot3.将两个集合归并成一个集合Union4.查找集合数量SetCount 二、源码 前言 需要将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按一定的规…

文件操作学习总结

磁盘上的⽂件是⽂件。 但是在程序设计中&#xff0c;我们⼀般谈的⽂件有两种&#xff1a; 程序⽂件、数据⽂件 &#xff08;从⽂件功能的⻆度来分类 的&#xff09;。 程序⽂件 &#xff1a; 程序⽂件包括源 程序⽂件&#xff08;后缀为.c&#xff09; , ⽬标⽂件&#xff0…

产品需求分析师的职责内容(合集)

产品需求分析师的职责内容1 职责&#xff1a; 1、根据公司战略规划&#xff0c;负责妇产科相关平台产品的中长期规划; 2、组织需求调研、收集、分析、整理、提炼、用户的需求&#xff0c;分析形成可行性研究报告; 3、深入挖掘产品需求&#xff0c;管理用户及公司内部业务需求&a…

配置OSPF与BFD联动示例

1、OSPF与BFD联动 双向转发检测BFD&#xff08;Bidirectional Forwarding Detection&#xff09;是一种用于检测转发引擎之间通信故障的检测机制。 BFD对两个系统间的、同一路径上的同一种数据协议的连通性进行检测&#xff0c;这条路径可以是物理链路或逻辑链路&#xff0c;包…

贪吃蛇(三)绘制蛇身

绘制蛇身的逻辑不难&#xff0c;存储上面使用结构体。 第一行和第十九行绘制--其它行&#xff0c;绘制|&#xff0c;分别在头尾处。 (1) 扫描蛇身&#xff0c;如果扫描到则绘制[]。 (2) 扫描蛇身&#xff0c;如果扫描不到则绘制空白。 #include"curses.h"struct Sn…

文件操作入门指南

目录 一、为什么使用文件 二、什么是文件 2.1 程序文件 2.2 数据文件 2.3 文件名 三、文件的打开和关闭 3.1 文件指针 3.2 文件的打开和关闭 四、文件的顺序读写 ​编辑 &#x1f33b;深入理解 “流”&#xff1a; &#x1f342;文件的顺序读写函数介绍&#xff1a; …

爬虫实战案例 -- 爬取豆瓣读书网页内容

进入网站检查信息 , 确定请求方式以及相关数据 找到爬取目标位置 开始敲代码 # 链接网站 def url_link(url):res requests.get(url,headers headers)response res.textparse_data(response)# 爬取信息 def parse_data(data):msg <li\sclass"media\sclearfix…

在 CentOS 上使用 Docker 运行 RabbitMQ

在 CentOS 上使用 Docker 运行 RabbitMQ 使用Docker来运行RabbitMQ非常方便&#xff0c;以下是一个简单的步骤&#xff0c;以YAML配置文件方式创建和运行RabbitMQ容器。 构建容器 创建Docker Compose文件 创建一个docker-compose.yml文件&#xff0c;内容如下&#xff1a; …

C# 将 Word 转化分享为电子期刊

目录 需求 方案分析 相关库引入 关键代码 Word 转 Pdf Pdf 转批量 Jpeg Jpeg 转为电子书 实现效果演示 小结 需求 曾经的一个项目&#xff0c;要求实现制作电子期刊定期发送给企业进行阅读&#xff0c;基本的需求如下&#xff1a; 1、由编辑人员使用 Microsoft Word…

MyBatis-Plus如何 关闭SQL日志打印

前段时间公司的同事都过来问我&#xff0c;hua哥公司的项目出问题了&#xff0c;关闭不了打印sql日记&#xff0c;项目用宝塔自己部署的&#xff0c;磁盘满了才发现大量的打印sql日记&#xff0c;他们百度过都按照网上的配置修改过不起作用&#xff0c;而且在调试时候也及为不方…

mysql的asc和desc全称

原文&#xff1a;http://t.csdnimg.cn/BJ2sUhttp://t.csdnimg.cn/BJ2sU

[AutoSar]基础部分 RTE 02 S/R Port 显式/隐式

目录 关键词平台说明一、显式&#xff08;Explicit&#xff09;和隐式&#xff08;Implicit&#xff09;1.1 显式模式1.1.1code 二、隐式模式2.1 code 三、区别 关键词 嵌入式、C语言、autosar、EcuM、Rte 平台说明 项目ValueOSautosar OSautosar厂商vector芯片厂商TI编程语…

字符串逆序输出

逆序输出就是本来abc输出的&#xff0c;然后我想让他输出成cba&#xff0c;那么我们还是要用到for循环&#xff0c;只不过原先是从零开始往上加&#xff0c;这回呢&#xff0c;是从上面往下减 我们观察上面这个图片&#xff0c;我们想要输出olleh&#xff0c;那么我们就要从4开…