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

在这里插入图片描述


" Jetpack Compose - - Modifier 系列文章 "


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

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

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

    📑 《 深度解析 Compose 的 Modifier 原理 - - DrawModifier 》

    📑 《 深度解析 Compose 的 Modifier 原理 - - PointerInputModifier 》

    📑 《 深度解析 Compose 的 Modifier 原理 - - ParentDataModifier 》


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

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


📓 什么是 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,也可以使用它。

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

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

相关文章

强化学习(二)多臂老虎机 “Multi-armed Bandits”——1

将强化学习与机器学习、深度学习区分开的最重要的特征为&#xff1a;它通过训练中信息来评估所采取的动作&#xff0c;而不是给出正确的动作进行指导&#xff0c;这极大地促进了寻找更优动作的需求。 1、多臂老虎机&#xff08;Multi-armed Bandits&#xff09;问题 赌场的老虎…

springBoot如何动态切换数据源

项目背景&#xff1a;最近公司中需要搭建mysql的主从&#xff0c;想着在spring中集成多数据源。mybatisplus提供的有插件用DS注解就能够实现&#xff0c;但是这种在mysql服务宕机的情况下不能够进行自动切换&#xff0c;于是就想着用aop自定义注解的方式来实现 项目实现效果&a…

el-tree获取当前选中节点及其所有父节点的id(包含半选中父节点的id)

如下图,我们现在全勾中的有表格管理及其下的子级,而半勾中的有工作台和任务管理及其子级 现在点击保存按钮后,需要将勾中的节点id及该节点对应的父节点,祖先节点的id(包含半选中父节点的id)也都一并传给后端,那这个例子里就应该共传入9个id,我们可以直接将getCheckedK…

架构篇07-复杂度来源:低成本、安全、规模

文章目录 低成本安全规模小结关于复杂度来源,前面的专栏已经讲了高性能、高可用和可扩展性,今天我们来聊聊复杂度另外三个来源低成本、安全和规模。 低成本 当我们的架构方案只涉及几台或者十几台服务器时,一般情况下成本并不是我们重点关注的目标,但如果架构方案涉及几百…

【Docker】在centos中安装nginx

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是平顶山大师&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《【Docker】安装nginx》。&#x1f3af;&#…

Macos flatter(用于快速LLL)本地编译安装(解决安装过程各种疑难杂症)

flatter是一个开源项目&#xff0c;能大大提高LLL的速度&#xff0c;项目提供的安装文档适用于Ubuntu&#xff0c;但是在macos上安装&#xff0c;总会遇到各种各样的问题&#xff0c;这里记录下所踩坑&#xff0c;帮助大家快速在macos上安装flatter。 文章目录 1.安装依赖库&am…

【linux进程间通信(一)】匿名管道和命名管道

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; 进程间通信 1. 前言2. 进程间…

计算机毕业设计 基于SpringBoot的红色革命文物征集管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

CSS 实现卡片以及鼠标移入特效

CSS 实现卡片以及鼠标移入特效 文章目录 CSS 实现卡片以及鼠标移入特效0、效果预览默认鼠标移入后 1、创建卡片组件2、添加样式3、完整代码 0、效果预览 默认 鼠标移入后 在本篇博客中&#xff0c;我们将探讨如何使用 CSS 来实现卡片组件&#xff0c;并添加鼠标移入特效&#…

第五回 九纹龙剪径赤松林 鲁智深火烧瓦罐寺 Webmin傻瓜式配置服务器

话说鲁智深走过数个山坡&#xff0c;看见一所败落的寺庙&#xff0c;上写“瓦罐之寺”。庙里空无一人&#xff0c;于是鲁智深径直走到后面厨房&#xff0c;发现有几个老和尚。鲁智深向和尚们讨要饭食&#xff0c;和尚们初时推说没有。然而&#xff0c;鲁智深眼尖&#xff0c;发…

【Python程序开发系列】一文搞懂argparse模块的常见用法(案例+源码)

一、引言 argsparse是python的命令行解析的标准模块&#xff0c;内置于python&#xff0c;不需要安装。这个库可以让我们直接在命令行中就可以向程序中传入参数并让程序运行。 在运行深度学习程序时。往往会因为电脑配置不行导致程序运行慢卡&#xff0c;需要将程序在虚机上进行…

【React基础】– JSX语法

文章目录 认识JSX为什么React选择了JSXJSX的使用 React事件绑定this的绑定问题事件参数传递 React条件渲染React列表渲染列表中的key JSX的本质createElement源码Babel官网查看直接编写jsx代码 虚拟DOM的创建过程jsx – 虚拟DOM – 真实DOM声明式编程 阶段案例练习 认识JSX ◼ …

手动添加测试用例配置输入参数和期望值

1.选中函数&#xff0c;点击右键选择插入测试用例。这里所选择的插入测试用例区别于之前的测试用例的地方在于&#xff0c;这里插入测试用例是手动配置的&#xff0c;之前的是自动生成的。手动配置可以自定义选择输入参数和期望值。 2.添加测试用例后&#xff0c;点击测试用例&…

高光谱分类论文解读分享之Grid Network: 基于各向异性视角下特征提取的高光谱影像分类

IEEE GRSL 2023&#xff1a;Grid Network: 基于各向异性视角下特征提取的高光谱影像分类 题目 Grid Network: Feature Extraction in Anisotropic Perspective for Hyperspectral Image Classification 作者 Zhonghao Chen , Student Member, IEEE, Danfeng Hong , Senior …

Conmi的正确答案——使用eclipse进行ESP32C3的debug

eclipse IDE 版本:2023-12 1、安装debug环境 参考大神的教程:【图文】手把手教你使用 Eclipse IDE 开发 ESP32 (这里是为了我下次回来速通才写的部分) 1.1、安装插件(plug-in,新的软件已经写成software了): 相关软件参数: 汉化(安装完成会提示重启应用): Name:…

【前后端的那些事】15min快速实现图片上传,预览功能(ElementPlus+Springboot)

文章目录 Element Plus SpringBoot实现图片上传&#xff0c;预览&#xff0c;删除效果展示 1. 后端代码1.1 controller1.2 service 2. 前端代码2.1 路由创建2.2 api接口2.2 文件创建 3. 前端上传组件封装 前言&#xff1a;最近写项目&#xff0c;发现了一些很有意思的功能&…

node.js 实现文件上传 和图片映射 文件下载(multer)

Muter是一个node.js中间件。主要处理multupart/from-data类型的表单数据&#xff0c;常用于上传文件。在express.js应用中&#xff0c;multer使得上传文件变得更加简单。主要功能是将客户端上传的文件存储在服务器的本地文件系统中。它还添加了一个body对象以及file或files对象…

qnx 上screen + egl + opengles 最简实例

文章目录 前言一、qnx 上的窗口系统——screen二、screen + egl + opengles 最简实例1.使用 addvariant 命令创建工程目录2. 添加源码文件3. common.mk 文件4. 编译与执行总结参考资料前言 本文主要介绍如何在QNX 系统上使用egl和opengles 控制GPU渲染一个三角形并显示到屏幕上…

数据结构与算法-二叉树-从前序与中序遍历序列构造二叉树

从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7…

Ubuntu18.04在线镜像仓库配置

在线镜像仓库 1、查操作系统版本 rootubuntu:~# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.5 LTS Release: 18.04 Codename: bionic 2、原文件备份 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak 3、查…