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

发布时间:2024年01月05日

众所周知:原理性分析的文章,真的很难讲的通俗易懂,讲的简单了就没必要写了,讲的繁琐难懂往往大家也不乐意看,所以只能尽量找个好的角度(比如从 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,也可以使用它。
文章来源:https://blog.csdn.net/pepsimaxin/article/details/135385571
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。