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

news2024/9/23 23:21:50

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 = initial
        override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initial
        override fun any(predicate: (Element) -> Boolean): Boolean = false
        override fun all(predicate: (Element) -> Boolean): Boolean = true
        override infix fun then(other: Modifier): Modifier = other
        override 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 就用传入的 Modifier
                Custom()  // 不传,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 = color
            properties["color"] = color
            properties["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,就返回什么 Modifier
    override 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.coloradmin.cn/o/1324768.html

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

相关文章

二阶多智能体的一致性-包含matlab仿真代码

模型 这里仅用一个简单的双积分模型 { x ˙ i v i v ˙ i u i \begin{equation} \begin{cases} {\dot x}_i v_i \\ {\dot v}_i u_i \\ \end{cases} \end{equation} {x˙i​vi​v˙i​ui​​​​ 我们的控制最终的期望是使得状态趋于一致&#xff0c;即 lim ⁡ t → ∞ ∣…

2024年云渲染哪个便宜?超实惠不排队的云渲染农场推荐

随着云计算技术的进步&#xff0c;云渲染逐渐成为动画制作和视觉效果产业中的首选技术。然而&#xff0c;对于许多创作者来说&#xff0c;寻找既经济又可靠的云渲染提供商一直是个重点问题。在众多云渲染提供商中&#xff0c;一家以其超值的性价比而闻名的云渲染农场尤其受到青…

Windows下安装MongoDB实践总结

本文记录Windows环境下的MongoDB安装与使用总结。 【1】官网下载 官网下载地址&#xff1a;Download MongoDB Community Server | MongoDB 这里可以选择下载zip或者msi&#xff0c;zip是解压后自己配置&#xff0c;msi是傻瓜式一键安装。这里我们分别对比进行实践。 【2】ZI…

Hudi 表类型和查询类型

数据湖hudi的表类型定义了数据在DFS上如何组织布局&#xff0c;同时实现一些timeline等操作&#xff08;表类型定定义数据是如何写入的&#xff09;&#xff1b;查询类型则是定义如何读取DFS上的数据。 Table typequery typeCopy-On-Write 快照查询&#xff1b; 增量查询&…

【数据分享】2019-2023年我国地级市逐年二手房房价数据(免费获取/Excel/Shp格式)

房价是一个城市发展程度的重要体现&#xff0c;一个城市的房价越高通常代表这个城市越发达&#xff0c;对于人口的吸引力越大&#xff01;因此&#xff0c;房价数据是我们在各项城市研究中都非常常用的数据&#xff01;之前我们分享了2019—2023年我国地级市逐月的二手房房价数…

VWAP 订单的最佳执行方法:随机控制法

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…

React基础巩固日志2

今天开始学习理解如何使用 props向组件传递数据和事件处理函数 在 React 中&#xff0c;props 是组件之间交互的一种方式&#xff0c;它允许你将数据从一个组件传递到另一个组件&#xff0c;下面我书写一个demo&#xff0c;以便于我更好的理解组件之间传值 这个例子中&#xff…

基于遥感数据的地表蒸散量的获取与分析的解决方案

1.引言 蒸散是指水分从地表移向大气的过程,它包括土壤与植株表面液相或固相水的蒸发和通过植物组织的蒸腾。蒸散过程是土壤、植被、大气系统中水分运移、转化的重要环节,因此准确的估算区域蒸散量能够有效提高气象预测以及水文气象预测的精度, 同时蒸散量的精确估算对于地理、…

OpenCV | ROI ——region of interest 感兴趣的区域(车道线掩码)

import cv2 import numpy as npedges_img.jpg edge_img cv2.imread(edges_img.jpg,cv2.IMREAD_GRAYSCALE) mask np.zeros_like(edge_img) mask cv2.fillPoly(mask,np.array([[[81,240],[212,143],[230,143],[386,238]]]),color255)#像素点用画图就能测出来 把鼠标放在图片…

第十七章 : Spring Boot 集成RabbitMQ(一)

第十七章 &#xff1a; Spring Boot 集成RabbitMQ&#xff08;一&#xff09; 前言 本章介绍RabbitMQ的核心概念和消息中间件中非常重要的协议——AMQP协议&#xff0c;然后介绍Direct、Topic、Headers、Fanout等交换机的作用和特点&#xff1b;RabbitMQ的五种消息发送模式-简…

Bert-vits2-v2.2新版本本地训练推理整合包(原神八重神子英文模型miko)

近日&#xff0c;Bert-vits2-v2.2如约更新&#xff0c;该新版本v2.2主要把Emotion 模型换用CLAP多模态模型&#xff0c;推理支持输入text prompt提示词和audio prompt提示语音来进行引导风格化合成&#xff0c;让推理音色更具情感特色&#xff0c;并且推出了新的预处理webuI&am…

图神经网络 (GNN) 概述

GNN 作者 with DALLE 3 一、说明 神经网络是受人脑工作启发的计算模型&#xff0c;能够从复杂的非结构化数据&#xff08;如图像、文本、音频和视频&#xff09;中学习。然而&#xff0c;还有许多其他类型的数据无法用传统的神经网络轻松表示&#xff0c;例如那些具有图形结构的…

实验4.2 默认路由和浮动静态路由的配置

实验4.2 默认路由和浮动静态路由的配置 一、任务描述二、任务分析三、具体要求四、实验拓扑五、任务实施1.路由器的基本配置。2.配置默认路由&#xff0c;实现全网互通。3.配置浮动静态路由&#xff0c;实现链路备份。 六、任务验收七、任务小结八、知识链接1&#xff0e;默认路…

tensorflow入门 自定义模型

前面说了自定义的层&#xff0c;接下来自定义模型&#xff0c;我们以下图为例子 这个模型没啥意义&#xff0c;单纯是为了写代码实现这个模型 首先呢&#xff0c;我们看有几个部分&#xff0c;dense不需要我们实现了&#xff0c;我们就实现Res&#xff0c;为了实现那个*3,我们…

Axure情形动作篇(ERP登录效验)

目录 一、ERP系统用户登录效验 1.1 完成步骤 1.2 最终效果 二、省市区联动 三、ERP菜单栏页面跳转 四、下拉加载效果实现 4.1 加载动画实现步骤 4.2 下划界面加载实现 4.3 最终效果 一、ERP系统用户登录效验 1.1 完成步骤 首先搭建ERP系统的登录界面&#xff08;输入…

Codeforces Round 916 (Div. 3)(G未补)

目录 A. Problemsolving Log B. Preparing for the Contest C. Quests D. Three Activities E1.E2. Game with Marbles F. Programming Competition A. Problemsolving Log 题意&#xff1a;A任务需要一分钟完成&#xff0c;B任务需要两分钟完成&#xff0c;……以此类推…

Dockerfile指令参考

写在前面 这里是原文链接&#xff0c;本文学习Dockerfile中的指令。 指令表格 指令描述ADD添加本地文件或远程文件到imageARG环境变量CMD运行container时执行的命令COPY复制文件或目录到imageENTRYPOINT运行container时执行的命令&#xff08;优先级高&#xff09;ENV环境变…

Hudi Clustering

核心概念 Hudi Clustering对于在数据写入和读取提供一套相对完善的解决方案。它的核心思想就是&#xff1a; 在数据写入时&#xff0c;运行并发写入多个小文件&#xff0c;从而提升写入的性能&#xff1b;同时通过一个异步&#xff08;也可以配置同步&#xff0c;但不推荐&…

SQL注入绕过正则及无列名注入

渗透测试 一、select\b[\s\S]*\bfrom正则二、科学计数法绕过三、过滤information四、无列名注入1、利用 join-using 注列名。2、无列名查询 五、报错注入7大常用函数1.ST_LatFromGeoHash()&#xff08;mysql>5.7.x&#xff09;payload 2.ST_LongFromGeoHash&#xff08;mysq…

3 - Electron app BrowserWindow对象-关于窗口

优雅的打开应用~ 当加载缓慢&#xff0c;打开应用的一瞬间会出现白屏&#xff0c;以下方法可以解决 const mainWindow new BrowserWindow({ show: false }) mainWindow.once(ready-to-show, () > {mainWindow.show() }) 设置背景颜色 const win new BrowserWindow({ b…