深度解析 Compose 的 Modifier 原理 -- Modifier.composed()、ComposedModifier

news2024/11/17 21:50:16

众所周知:原理性分析的文章,真的很难讲的通俗易懂,讲的简单了就没必要写了,讲的繁琐难懂往往大家也不乐意看,所以只能尽量找个好的角度(比如从 Demo 代码示例出发)慢慢带着大家去钻源码,如果确实能帮助到大家理解清楚原理,那就点个赞呗~😄

在正式开始分析 Modifier 相关原理之前,建议你先看看 Compose 是如何将数据转换成 UI 的?这篇文章,当你了解了 Compose 的“组合”、“布局”、“绘制”的思维模型后,有助于你更透彻的了解 Modifier 的原理。


Modifier 系列文章

  📑 《 深入解析 Compose 的 Modifier 原理 - - Modifier、CombinedModifier 》

  📑 《 深度解析 Compose 的 Modifier 原理 - - Modifier.composed()、ComposedModifier 》

  📑 《 深入解析 Compose 的 Modifier 原理 - - LayoutModifier 和 Modifier.layout 》


📓 什么是 ComposedModifier ?


首先看下 ComposedModifier 类:

private open class ComposedModifier(
    inspectorInfo: InspectorInfo.() -> Unit,
    val factory: @Composable Modifier.() -> Modifier
) : Modifier.Element, InspectorValueInfo(inspectorInfo)

可以发现 ComposedModifier 是个私有函数,你是没有办法直接创建的。所以如果我们要创建一个 ComposedModifier,则需要使用 Modifier.composed() 扩展函数:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Modifier.composed {  }
            }
        }
    }
}

这样,Modifier.composed() 扩展函数会帮我们创建出一个 ComposedModifier。

fun Modifier.composed(
    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
    factory: @Composable Modifier.() -> Modifier
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))

看到这里,你应该会有疑问三连:

  1. ComposedModifier 是个啥?
  2. 为什么要创建一个 ComposedModifier?
  3. 什么时候又该创建一个 ComposedModifier?

带着这三个疑问,我们开始继续探讨 Modifier 的精髓…

首先我直接告诉你 ComposedModifier 的作用:ComposedModifier 的作用是通过 factory 工厂函数在组合过程时执行 factory 创建出 Modifier 并使用。

这段定义看不懂没关系,跟着我的节奏来,比如看下面这段简单的代码示例:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Modifier.padding(10.dp)
            }
        }
    }
}

正常情况下这么写,当执行到 Modifier.padding() 的时候,会立即创建出一个 LayoutModifier,这个你应该很清楚了,如果不了解,可以先阅读 【 深度解析 Compose 的 Modifier 原理 – Modifier、CombinedModifier 】这篇文章。

现在我调整下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Modifier.composed { Modifier.padding(10.dp) }
            }
        }
    }
}

这里我把 Modifier.padding() 给包进了 ComposedModifier 里面,那么当程序执行到这段代码的时候,你觉得 Modifier.padding() 还会立即执行吗?

再来看一遍 ComposedModifier 的定义:ComposedModifier 的作用是通过 factory 工厂函数在组合过程时执行 factory 创建出 Modifier 并使用。

这个时候我们可以拆分下定义了,主要就是两个核心

  1. 组合过程中
  2. 执行 factory 创建出 Modifier

先来看下第二点,我们其实从刚刚 Modifier.composed 函数定义就能看出来了:


在这里插入图片描述


那么第一点:组合过程中,该如何理解?其实很好理解,你既然创建了 Modifier,肯定要用起来,那么我们就来看看在组合过程中 ComposedModifier 是如何发挥作用的。

我现在再来改下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.composed { Modifier.padding(10.dp) })
            }
        }
    }
}

再来看一遍 ComposedModifier 的定义:ComposedModifier 的作用是通过 factory 工厂函数在组合过程时执行 factory 创建出 Modifier 并使用。

所以现在我们就来看看 Box() 内部是在哪里执行 factory 工厂函数的。

  1. 跳转到 Box():
// Box.kt

@Composable
fun Box(modifier: Modifier) {
    Layout({}, measurePolicy = EmptyBoxMeasurePolicy, modifier = modifier)
}
  1. 跳转到 Layout():
// Layout.kt

@Composable inline fun Layout(
    content: @Composable @UiComposable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
	... ...
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        ... ...
        skippableUpdate = materializerOf(modifier),
        content = content
    )
}

@PublishedApi
internal fun materializerOf(
    modifier: Modifier
): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
    val materialized = currentComposer.materialize(modifier)
    ... ...
}
  1. 跳转 materialize()
// ComposedModifier.kt

fun Composer.materialize(modifier: Modifier): Modifier {
    ... ...

    val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
        acc.then(
            if (element is ComposedModifier) {
                @Suppress("UNCHECKED_CAST")
                val factory = element.factory as Modifier.(Composer, Int) -> Modifier
                val composedMod = factory(Modifier, this, 0)
                materialize(composedMod)
            } else {
                element
            }
        )
    }

    endReplaceableGroup()
    return result
}

好熟悉呀,出现了 foldIn()!来来,我们解读一下这段代码。


在这里插入图片描述


这么一分解,现在你应该很清楚 ComposedModifer 的工厂函数在哪里调用了吧?

但是问题又来了,这么费劲搞一个 ComposedModifier 有啥意义?

Box(Modifier.composed { Modifier.padding(10.dp) })
Box(Modifier.padding(10.dp)  // 我这么写不就行了?

我们来看下 Modifier.composed 函数的说明:

在这里插入图片描述

注意几个关键词:1. stateful modifiers:有状态的 Modifier;2. reused:重用;3. element-specific state:状态独有

该如何理解这几个词的意思呢?

来看以下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                // 写法 1
                val modifier1 = Modifier.composed { Modifier.padding(10.dp) }
                Box(modifier1)
                Text("Compose", modifier1)

                // 写法 2
                val modifier2 = Modifier.padding(10.dp)
                Box(modifier2)
                Text("Compose", modifier2)
            }
        }
    }
}

请问:写法 1 和写法 2 有什么区别?

我相信你经过上面 ComposedModifier 的解析流程,应该可以很清晰知道:写法 1 和写法 2 唯一的区别就在于,写法 1 的 Modifier.padding() 会延迟创建(由 ComposedModifier 的 factory 函数创建),它们两者的运行显示结果没有任何区别。

实际上 Modifier.composed() 不是用在案例写的这种场景下,而是用在有状态的 Modifier 的场景。

什么叫有状态的 Modifier?按上面的例子 10.dp 就是一个状态,不过因为在这里已经写死了数值所以 Modifier.padding() 是无状态的,进而 Modifier.composed() 也是无状态的。

现在我们来改下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                val modifier = Modifier.composed {
                    var padding by remember { mutableStateOf(10.dp) }
                    Modifier.padding(padding)
                }
                Box(modifier)
                Text("Compose", modifier)
            }
        }
    }
}

我们把 10.dp 提取出来,这样 Modifier.padding() 就是有状态的了。如果你对 Compose 所谓的有状态、无状态不了解的话,可以看看 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” – 有状态、无状态、状态提升?】 这篇文章。

现在 Modifier.padding() 就是有状态的,进而 Modifier.composed() 就是有状态的,现在我们再回顾下 Modifier.composed 函数注解:

1. stateful modifiers:有状态的 Modifier;2. reused:重用;3. element-specific state:状态独有

再结合这个例子:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                val modifier = Modifier.composed {
                    var padding by remember { mutableStateOf(10.dp) }
                    Modifier.padding(padding)     // 1. stateful modifiers:有状态的 Modifier
                }
                Box(modifier)                     // 2. reused:重用
                Text("Compose", modifier)         // 2. reused:重用
            }
        }
    }
}

是不是满足了两条?那 “状态独有” 又是什么意思?该怎么验证呢?

来,我们再改下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                val modifier = Modifier.composed {
                    var padding by remember { mutableStateOf(10.dp) }
                    Modifier
                        .padding(padding)
                        .clickable { padding = 0.dp }
                }
                Column {
                    Box(Modifier.background(Color.Red) then modifier)
                    Text("Compose", Modifier.background(Color.Blue) then modifier)
                }
            }
        }
    }
}

这里我们对 Modifier 又加了一个点击操作:修改 padding 值为 0,看下运行效果:

在这里插入图片描述

发现什么没?Text() 组件点击后,padding 修改为 0,却没有影响到 Box 的 padding,这就是 Modifier 状态独有!

那我们再试试不用 Modifier.composed() 函数包起来的场景,比如代码如下:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                var padding by remember { mutableStateOf(10.dp) }
                val modifier = Modifier
                        .padding(padding)
                        .clickable { padding = 0.dp }
                Column {
                    Box(Modifier.background(Color.Red) then modifier)
                    Text("Compose", Modifier.background(Color.Blue) then modifier)
                }
            }
        }
    }
}

这段代码不难理解吧?来看下效果:

在这里插入图片描述

发现没?它们恭喜了 padding,点击任何一个组件,都会影响到另外一个的 padding。

所以到这里,我们就可以总结下 Modifier.compose() 函数的作用了:它会创建一个带状态的 Modifier,这个 Modifier 可以重用,并且状态是独立的。

但是!但是!但是!细心的你应该又会有一个疑问:这有点费啊,我可以有更简单的写法啊,比如:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                var padding1 by remember { mutableStateOf(10.dp) }
                val modifier1 = Modifier.padding(padding1).clickable { padding1 = 0.dp }
                var padding2 by remember { mutableStateOf(10.dp) }
                val modifier2 = Modifier.padding(padding2).clickable { padding2 = 0.dp }
                Column {
                    Box(Modifier.background(Color.Red) then modifier1)
                    Text("Compose", Modifier.background(Color.Blue) then modifier2)
                }
            }
        }
    }
}

既然不想 Box() 和 Text() 的 padding 相互影响,那么分别给它们设置一个 Modifier 不就行了?何必要用 Modifier.composed() 这种花里胡哨,看上去更高端的方法呢?这段代码的逻辑看上去岂不是更清晰?

实际上 Modifier.composed() 的使用场景是:当我们需要创建的 Modifier 需要给它添加一些内部状态,这时候我们需要使用 Modifier.composed() 来为它提供一个 Composable 的上下文环境,从而让我们可以使用 remember。

怎么理解这段话?你可以理解成:当我们需要自定义一个 Modifier 时,同时当 Modifier 内部需要用到 remember 时,就需要用到 ComposeModifier。

比如我们修改下前面的 Modifier.composed() 代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Column {
                	// 2. 给外部组件调用
                    Box(Modifier.background(Color.Red) then Modifier.paddingJumpModifier())
                    Text("Compose", Modifier.background(Color.Blue) then Modifier.paddingJumpModifier())
                }
            }
        }
    }
}

// 1. 这里自定义了一个 Modifier.paddingJumpModifier
fun Modifier.paddingJumpModifier() {
    var padding by remember { mutableStateOf(10.dp) }
    Modifier
        .padding(padding)
        .clickable { padding = 0.dp }
}

如果你这么写会报错:

在这里插入图片描述

报错原因很简单,remember 是一个 Composable 函数,你必须要在 Composable 的上下文环境中才能调用。这时候我们就可以使用 Modifier.composed() 为它提供一个 Composable 的上下文环境,从而让我们可以使用 remember。

写法很简单:

fun Modifier.paddingJumpModifier() = composed {
    var padding by remember { mutableStateOf(10.dp) }
    Modifier
        .padding(padding)
        .clickable { padding = 0.dp }
}

这样你的代码就不会报错了,并且所有调用 Modifier.paddingJumpModifier 的地方内部的 padding 状态也是独立的。

为什么 Modifier.composed() 函数内部就可以使用 remember ?

fun Modifier.composed(
    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
    factory: @Composable Modifier.() -> Modifier  // 看这里,它的工厂函数提供了 Composable 上下文环境
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))

所以,factory 工厂函数提供了 Composable 上下文环境,我们就可以调用 remember。

除了可以调用 remember,我们也可以在 Modifier.compose() 内部开启协程:

fun Modifier.paddingJumpModifier() = composed {
    var padding by remember { mutableStateOf(10.dp) }
    LaunchedEffect(Unit) {
        
    }
    Modifier
        .padding(padding)
        .clickable { padding = 0.dp }
}

如果你不加 = composed,那么你调用 LaunchedEffect 就会报错,不信你试试。

最后总结一下 ComposedModifier 或 Modifier.composed():

  1. 按照官方注释的说法,Modifier.composed() 能创建出带有状态的 Modifier,让这个 Modifier 在多处被重用;
  2. 有状态的 Modifier 就是被 remember 包着的变量和 Modifier 放在一起作为它的内部状态使用;
  3. 重用就是 Modifier.composed() 在每一个使用的地方都创建一个内部状态独立的 Modifier,而不会互相影响
  4. Modifier.composed() 不仅能提供内部状态,在一些需要 Composable 上下文环境,例如 LaunchedEffect 或 CompositionLocal 等地方使用 Modifier,也可以使用它。

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

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

相关文章

书生·浦语大模型全链路开源开放体系

书生浦语大模型全链路开源体系_哔哩哔哩_bilibili 大模型全链路开源开放体系等你来探索~ https://github.com/internLM/tutorial 书生浦语全链条开源开放体系 1&#xff09;数据: 书生万卷 2TB数据&#xff0c;并行训练&#xff0c;极致优化涵盖多种模态与任务 预训练: I…

聚道云软件连接器助力某动漫行业公司实现财务自动化

客户介绍 某动漫行业公司是一家专注于文化创意领域&#xff0c;致力于为人们提供独特、有趣的文化产品。公司拥有一支充满活力和创造力的团队&#xff0c;他们以卓越的创意和精湛的技术&#xff0c;创造出了一系列令人惊叹的作品。未来&#xff0c;该公司将继续秉承这一理念&a…

低代码开发平台支持复杂的业务逻辑和API对接吗

当今数字化时代&#xff0c;企业和组织面临着日益复杂的业务需求。为了应对这些挑战&#xff0c;低代码开发平台应运而生。白码低代码开发平台是一种高效、灵活且可扩展的开发工具&#xff0c;能够支持复杂的业务逻辑和第三方API对接&#xff0c;为企业带来更快速、更灵活的解决…

Rust 字符串 初步了解

rust 的字符串 。字符串不是复合类型&#xff0c; String 和 &str &#xff1a; String 具有所有权&#xff0c;是存储在堆上的。&str 没有所有权&#xff0c;是对 String 的引用。字符串字面量也是 &str 类型&#xff0c;存储在栈上。 切片&#xff08;slice&a…

线性代数 --- 为什么LU分解中L矩阵的行列式一定等于(+-)1?

以下是关于下三角矩阵L的行列式一定等于-1的一些说明 证明&#xff1a;在LU分解中&#xff0c;下三角矩阵L的行列式一定是. 在证明之前&#xff0c;我这里先补充几条关于行列式的性质&#xff1a; 性质1&#xff1a;对于三角矩阵而言&#xff0c;不论是上三角矩阵还是下三角矩…

Qt6学习笔记:对象树

使用QObject及其子类创建的对象是以对象树的形式来组织的。创建一个QObject对象时若设置一个父对象&#xff0c;它就会被添加到父对象的子对象列表里&#xff0c;一个父对象被删除时&#xff0c;其全部的子对象都会自动删除。QObject类的构造函数里有一个参数用于设置对象的父对…

PyTorch基础操作

一、Tensor 在 PyTorch 中&#xff0c;张量&#xff08;Tensor&#xff09;是一个核心概念&#xff0c;它是一个用于存储和操作数据的多维数组&#xff0c;类似于 NumPy 的 ndarray&#xff0c;但与此同时&#xff0c;它也支持 GPU 加速&#xff0c;这使得在大规模数据上进行科…

数据结构和算法-希尔排序(增量序列 算法实现 性能分析 稳定性)

文章目录 希尔排序过程小结增量序列不是固定的 算法实现算法性能分析稳定性小结 希尔排序 基本有序&#xff0c;就是存在有序的子序列 通过增量4得到各个子表 对各个子表分别进行插入排序 缩小增量&#xff0c;再除2&#xff0c;此时的子表 对各个子表插入排序 缩小增量&…

数据库设计——DQL

D Q L \huge{DQL} DQL ⭐⭐⭐⭐⭐ DQL&#xff1a;数据库查询语言&#xff0c;用来查询数据库中的记录&#xff0c;非常的重要&#xff0c;对于数据库的操作修改相对来讲还是较少部分&#xff0c;绝大多数操作都是数据查询。 整体的语法结构&#xff1a; 基本查询 示例&#…

2024最新Java基础面试题大全(一)

1、String可以被继承&#xff1f; 不能被继承&#xff0c;因为String类有final修饰符&#xff0c;而final修饰的类是不能被继承的。 public final class String implements java.io.Serializable, Comparable<String>, CharSequence {// 省略...  }2、常见集合类 Java…

C语言编译器(C语言编程软件)完全攻略(第十七部分:Dev C++使用教程(使用Dev C++编写C语言程序))

介绍常用C语言编译器的安装、配置和使用。 十七、Dev C使用教程&#xff08;使用Dev C编写C语言程序&#xff09; 前面我们给出了一段完整的C语言代码&#xff0c;就是在显示器上输出“C语言中文网”&#xff0c;如下所示&#xff1a; #include <stdio.h> int main() {…

Java 新手如何使用Spring MVC RestAPI的加密

目录 前言 为什么需要加密RestAPI&#xff1f; 使用Spring Boot创建RestAPI 使用HTTPS加密RestAPI 使用Spring Security增加安全性 使用JWT实现令牌身份验证 使用Postman测试加密的RestAPI 总结 前言 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业…

HarmonyOS应用开发之DevEco Studio安装与初次使用

1、DevEco Studio介绍 DevEco Studio是基于IntelliJ IDEA Community开源版本打造&#xff0c;面向华为终端全场景多设备的一站式集成开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供工程模板创建、开发、编译、调试、发布等E2E的HarmonyOS应用/服务的开发工具。…

【谷歌云】注册谷歌云 创建Compute Engine

文章目录 一、Google Cloud注册1.1 账号信息1.2 付款信息验证1.3 验证成功 二、Compute Engine创建2.1 启动Compute Engine API2.2 创建实例2.3 新建虚拟机实例2.4 等待实例创建完成2.5 查看虚拟机配置信息2.6 创建防火墙规则2.7 SSH远程连接虚拟机 三、参考链接 一、Google Cl…

坐标经纬度的基本运算(2个坐标经纬度的距离、中心点坐标经纬度范围内的坐标计算)

现在的应用大都居于LBS服务&#xff0c;用户地理位置的获取&#xff08;经纬度坐标、所属行政区域&#xff09;&#xff0c;提供服务场所的地理位置也有行政区域信息和坐标信息。 用户与服务场所的联系&#xff0c;就近服务原则的设计&#xff0c;服务场所相对于用户的排序。 …

Java JDBC整合(概述,搭建,PreparedStatement和Statement,结果集处理)

一、JDBC的概述&#xff1a; JDBC&#xff1a;是一种执行sql语句的Java APL&#xff0c;可以为多种关系类型数据库提供统一访问&#xff0c;它由一组用Java语言编写的类和接口组成。有了JDBC&#xff0c;Java人员只需要编写一次程序就可以访问不同的数据库。 JDBC APL&#xf…

代码随想录算法训练营第17天 |110.平衡二叉树 257. 二叉树的所有路径 404.左叶子之和

目录 110.平衡二叉树 &#x1f4a1;解题思路 递归 &#x1f4bb;实现代码 257. 二叉树的所有路径 &#x1f4a1;解题思路 递归 &#x1f4bb;实现代码 404.左叶子之和 &#x1f4a1;解题思路 # 递归法 &#x1f4bb;实现代码 110.平衡二叉树 题目链接&#xff1a;…

Spring整合MyBatis项目代码示例

文章目录 Spring整合MyBatis项目代码示例1、创建如下结构的项目Spring_MyBatis2、在pom.xml文件中添加以下依赖并刷新maven3、在resources文件夹下添加spring等配置文件&#xff08;applicationContext.xml&#xff0c;db.properties&#xff0c;log4j.properties&#xff09;4…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)主线程反应堆模型的事件添加和处理详解

>>服务器和客户端建立连接和通信流程&#xff1a; 基于多反应堆模型的服务器结构图&#xff0c;这主要是一个TcpServer&#xff0c;关于HttpServer,主要是用了Http协议&#xff0c;核心模块是TcpServer。这里边有两种线程&#xff1a;主线程和子线程。子线程是在线程池里…

LeGO-LOAM 几个特有函数的分析(2)

接上回LeGO-LOAM 几个特有函数的分析&#xff08;1&#xff09; 二、广度优先遍历 广度优先遍历&#xff08;Breadth-First Search, BFS&#xff09;是一种用于遍历或搜索树或图的算法。这种算法从树的根&#xff08;或图的某一指定节点&#xff09;开始&#xff0c;然后探索…