Jetpack Compose 深入探索系列四: Compose UI

这篇具有很好参考价值的文章主要介绍了Jetpack Compose 深入探索系列四: Compose UI。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

通过 Compose runtime 集成 UI

Compose UI 是一个 Kotlin 多平台框架。它提供了通过可组合函数发出 UI 的构建块和机制。除此之外,这个库还包括 Android 和 Desktop 源代码,为 Android 和 Desktop 提供集成层。

JetBrains积极维护Desktop代码库,而Google维护Android和通用代码库。Android和Desktop源代码库都依赖于通用源代码库。到目前为止,Web还没有出现在Compose UI中,因为它是使用DOM构建的。

当使用 Compose runtime 集成 UI 时,目标是构建用户可以在屏幕上体验的布局树。这个树是通过执行发出 UI 的 Composable 函数来创建和更新的。用于树的节点类型只有 Compose UI 知道,所以 Compose runtime 可以不知道它。即使 Compose UI 本身已经是一个Kotlin多平台框架,它的节点类型到目前为止只被 Android 和 Desktop 支持。其他库,如 Compose for Web 使用不同的节点类型。出于这个原因,客户端库发出的节点类型必须只有客户端库知道,并且 runtime 将从树中插入、删除、移动或替换节点的操作委托给客户端库。我们将在后面讨论这个问题。

初始组合和后续的重组过程都参与了布局树的构建和更新过程。这些过程执行我们的 Composable 函数,这使它们能够安排从树中插入、删除、移动或替换节点的更改。这将创建一个更改列表,稍后将使用 Applier 遍历这些更改,以检测影响树结构的更改,并将这些更改映射到树的实际更改,以便最终用户可以体验它们。 如果我们在初始的组合过程中,这些更改将插入所有节点,从而建立我们的布局树。如果我们在重组过程中,他们会被更新。当我们的可组合函数的输入数据(即参数或读取的可变状态)发生变化时,将触发重组。

从 Compose UI 的角度来看组合

如果我们以 Android 集成为例,从 Compose UI 库进入 runtime 的更频繁的入口点发生在我们调用setContent 时,可能是针对我们的一个屏幕。

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI

但是屏幕 (例如Android中的Activity/Fragment) 并不是我们可以找到setContent调用的唯一地方。它也可以发生在我们的视图层次结构的中间,例如,通过 ComposeView (例如在一个混合 Android App里)。

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI
在本例中,我们通过编程方式创建视图,但它也可以是应用程序中通过XML定义的任何布局层次结构的一部分。

setContent函数创建了一个新的 root Composition,然后尽可能地重用它。我把它们称为“根”组合,因为每个组合都有一个独立的 Composable 树。这些组合之间没有任何联系。每个组合将像它所代表的 UI 一样简单或复杂。

在这种思维模式下,我们可以想象应用程序中有多个节点树,每个节点树都链接到不同的 Composition。让我们想象假如有一个包含 3 个Fragment的 Android 应用,其中Fragment1Fragment3调用setContenthook 它们的 Composable 树,而Fragment2在它的布局上声明了多个ComposeView(并调用setContent)。在这种情况下,我们的应用程序将有 5 个根 Composition,它们都完全独立。

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI
为了创建这些布局层次结构,相关的 Composer 将运行组合过程。相应 setContent 调用的所有 Composable 函数都将执行并发出它们的更改。对于 Compose UI ,这些更改就是插入、移动或替换 UI 节点,而这些操作通常是由构建 UI 的 block 代码块发出的。即:BoxColumnLazyColumn 等。即使这些 Composables 函数通常属于不同的库(foundationmaterial),它们最终都被定义为 Layoutcompose-ui),这意味着它们发出相同的节点类型:LayoutNode

LayoutNode 在之前介绍过,它是 UI block 的表示形式,因此在 Compose UI 中,它是用于根 Composition 最常用的节点类型。

任何 Layout Composable 函数都会将 LayoutNode 节点发射到 Composition 中,这是通过 ReusableComposeNode 发射的。(请注意,ComposeUiNode 是一个通用接口协议,LayoutNode 接口实现了它)

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI

这里发出一个更改,将可重用的节点插入或更新到 composition 中。这将适用于我们使用的任何 UI 构建 block 块。

可重用节点是 Compose runtime 中的一种优化。当节点的键(key)发生变化时,可重用节点允许 Compose 在重组时更新节点内容(就地更新),而不是将其丢弃并创建一个新节点。为了实现这一点,Composition 就像正在创建新的内容一样,但是 slot table 在重组时被遍历。这种优化仅适用于那些在发射调用过程中,可以完全被 setupdate 操作描述的节点,或者换句话说,不包含隐藏内部状态的节点。这对于 LayoutNode 是正确的,但对于 AndroidView 就不是这样了。因此,AndroidView 使用标准的 ComposeNode 而不是可重用节点。

在上面代码中,我们可以看到 ReusableComposeNode 会创建节点(通过factory工厂函数),初始化它( update lambda),并创建一个可替换的group组来包装所有内容。该group会被分配一个唯一的key,以便稍后可以识别它。通过可替换的group内的 content lambda 的调用而发出的任何节点,实际上都将成为该节点的子节点。

update block 块内的 set 方法调用会安排其后的 lambda 执行。这些 lambda 执行的时机是:节点第一次创建时,或对应属性的值自上次被 remembered 后已更改时。

这就是 LayoutNode 是如何被提供给我们应用中的多个 Compositions 的。这可能会让我们认为任何 Composition 都只包含 LayoutNode。但这是错误的!在 Compose UI 中,还有其他类型的 Compositions 和节点类型需要考虑。虽然 LayoutNodes 是最常用的节点类型,但还有其他类型的节点,比如 ModifierNodeAttachNodes 等等。它们都是由 Composable 函数发出,但是不同于 LayoutNode,它们可能只代表对树上现有节点的修改而非全新节点的插入或替换。因此,runtime 需要有一些机制来处理这些不同类型的节点并将它们合并到同一棵树中。

从 Compose UI 的角度来看子组合(Subcomposition)

Composition 不仅仅存在于 root 级别。我们也可以在Composable 树的更深层次中创建Composition,并将其链接到其父Composition。这就是Compose所谓的 Subcomposition

我们在前面学到的一件事是,Composition可以连接为树形结构。也就是说,每个Composition 都有一个指向其父 CompositionContext 的引用,该引用代表其父 Composition(当root的parent为Recomposer本身时除外)。这就是 runtime 如何确保CompositionLocals和无效化可以像单个Composition一样在树中向下传播的方式。

在Compose UI中,创建 Subcomposition 主要有两个原因:

  • 推迟初始组合过程,直到某些信息已知。
  • 修改子树生成的节点类型。

让我们来讨论一下这两个原因。

延迟初始组合过程

我们有一个关于 SubcomposeLayout 例子,它类似于 Layout,会在布局阶段创建和运行一个独立的 Composition。这允许子 Composables 依赖于其计算的任何结果。例如,BoxWithConstraints 组件就是使用 SubcomposeLayout实现的,它在 block 块中公开其接收的父约束,以便可以根据它们调整其内容。以下是从官方文档中提取的示例,在BoxWithConstraints 中根据可用的最大高度在两个不同的 Composables 之间做出选择:

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI
Subcomposition 的创建者可以控制初始组合过程发生的时间,而 SubcomposeLayout 决定在布局阶段进行它,而不是在根组合时。

Subcomposition 允许独立于父 Composition 进行重组。例如,在 SubcomposeLayout中,每当发生 layout 布局时,传递给其 lambda 的参数可能会发生变化,这将触发重组。另一方面,如果从 Subcomposition 中读取的状态发生更改,则将在执行初始组合后为父组合安排重组。

从发出的节点方面考虑,SubcomposeLayout 也会发出一个 LayoutNode,因此子树使用的节点类型将与父 Composition 使用的节点类型相同。这引出了以下问题:是否可以在单个 Composition 中支持不同的节点类型

好吧,从技术上来讲是可以实现的,只要相应的 Applier 允许。这取决于节点类型的含义。如果使用的节点类型是多个子类型的共同父级,则可以支持不同的节点类型。即便如此,这样做可能会使Applier 的逻辑更加复杂。事实上,在 Compose UI 中可用的 Applier 实现都被固定为单个节点类型

也就是说,Subcomposition 实际上可以在子树中支持完全不同的节点类型,这是我们前面列出的使用 Subcomposition 的第二个原因。

更改子树中的节点类型

Compose UI 中有一个很好的例子可以解释这个概念:创建和显示矢量图形的Composable(例如:rememberVectorPainter)。

Vector Composables 是一个很好的研究案例,因为它们还创建了自己的 Subcomposition 来将矢量图形建模为一棵树。在组合时,Vector Composable 会发出一个不同于LayoutNode的节点类型来提供给其 SubcompositionVNode 类型。这是一个递归类型,用于建模独立的 PathsPaths 组。

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI

这里有一件有趣的事情需要思考,通常我们使用 VectorPainterImageIcon 或其他类似的 Composable 组件中来绘制这些矢量图形,就像我们在上面的代码片段中看到的那样。这意味着包含它的 Composable 是一个 Layout,因此它会发出一个 LayoutNode 到其关联的 Composition 中。但同时,VectorPainter 创建了自己的 Subcomposition 来模拟矢量图形的树形结构,并将其链接到前一个 Composition 中(前者会成为其父级)。以下是一个图形示例:

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI

这种配置使得 vector 子树(Subcomposition)可以使用不同的节点类型:VNode

Vectors 通过 Subcomposition 进行建模,因为通常可以从父组合中访问某些 CompositionLocals 以在 vector Composable 调用中使用(例如:rememberVectorPainter)。诸如主题颜色或density之类的东西就是很好的例子。

用于矢量图的子组合Subcomposition)会在其对应的 VectorPainter 离开父 Composition 时被销毁 。我们将在后面学习更多有关 Composable 生命周期的知识,但请记住,任何 Composable 都会在某个时刻进入和离开 Composition

现在我们已经对使用 Compose UI 的应用(Android 或 Desktop)中树的外观有了更完整的了解,其中通常存在 root CompositionSubcomposition

反映 UI 中的变化

在前面,我们已经了解了 UI 节点是如何通过初始化合成和后续的重组合成被发射到 runtime 中的。此时,runtime 会接管这些节点,并执行其工作。但这只是一面,还需要存在某种集成来在实际的 UI 中反映所有这些发射的更改,以便用户可以体验它们。这个过程通常被称为节点树的 Materialization (中文翻译过来可以叫实体化实例化具体化),这也是客户端库(Compose UI)的职责之一。

不同类型的 Appliers

Applier 是一个抽象层,runtime 依赖于它最终实现树中的任何更改。这反转了依赖关系,使 runtime 完全不依赖于使用库的平台。这个抽象层允许客户端库(如Compose UI)hook 自己的 Applier 实现,并使用它们自己的节点类型来集成平台。下面是一个简单的图示:

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI

顶部的两个方框(ApplierAbstractApplier)是 Compose runtime 的一部分。底部列出了一些 Applier 实现,它们由 Compose UI 提供。

AbstractApplierCompose runtime 提供的基本实现,用于在不同 applier 之间共享逻辑。它将已访问的节点存储在一个中,并维护对当前访问节点的引用,以便知道应该在哪个节点上执行操作。每当沿着树访问到一个新节点时,Composer 通过调用 applier#down(node:N) 来通知 Applier。这会将节点压入栈中,Applier 可以对其运行任何所需的操作。

每当访问者需要返回到父节点时,Composer 就会调用 applier#up(),这将从堆栈中弹出上次访问的节点。

让我们通过一个相当简单的例子来理解它。假设我们有以下需要 materializeComposable 树:

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI

condition 改变时,Applier 将会:

  • 接收一个 down() 调用以访问 Column节点。
  • 然后是另一个 down() 调用以进入 Row 节点。
  • 接着是删除(或插入,具体取决于condition)可选子 Text 节点。
  • 然后是一个 up() 调用以返回到父节点(Column)。
  • 最后,删除(或插入)第二个条件文本 Text 节点。

AbstractApplier 提供了堆栈downup 操作,因此子 applier 可以共享相同的导航逻辑,而不管它们使用的节点类型如何。这提供了节点之间的父子关系,因此在导航树时,从技术上讲,它消除了特定节点类型维护此关系的需要。即使如此,特定的节点类型仍然可以自由地实现自己的父子关系,如果它们碰巧需要它们用于特定于客户库的更特定的原因。

这实际上是 LayoutNode 的情况,因为并非所有操作都在组合期间执行。例如,如果由于某种原因需要重绘节点,则 Compose UI 会遍历父节点,以查找创建绘制节点所需的层的节点,以便在其上调用invalidate。所有这些操作都发生在组合之外,因此 Compose UI 需要一种方法来自由遍历树形结构。

让我们回顾一下在上一篇文章中,我们提到了 Applier 如何以从上到下从下到上的两种方式来构建节点树。我们还描述了每种方法的性能影响,并且这些影响取决于每次插入新节点时需要通知的节点数。( 可以点击这里回顾其中的构建节点树时的性能部分 )我想回顾一下这一点,因为Compose UI中实际上有两种构建节点树的策略的例子。这些策略由库使用的两种 Applier 实现。

Jetpack Compose 提供了两个 AbstractApplier 的实现来将 Android 平台与 Jetpack Compose runtime 集成:

  • UiApplier:用于呈现大多数 Android UI 。它将节点类型固定为 LayoutNode,因此它将实现我们树中的所有 Layouts 布局。
  • VectorApplier:用于呈现矢量图形。它将节点类型固定为 VNode,以表示和实现矢量图形。

如前面介绍的那样,这是两种节点类型。

到目前为止,这是 Android 提供的唯一两个实现,但是对于一个平台来说,可用的实现并不一定是固定的。如果需要表示不同于现有节点树的节点树,则未来可能会在 Compose UI 中添加更多实现。

根据访问的节点类型,将使用不同的 Applier 实现。例如:如果我们有一个使用 LayoutNodes 提供的 root Composition,以及使用 VNodes 提供的 Subcomposition,那么两个 Applier 都将被用于将完整的 UI 树实例化。

让我们快速浏览一下两种 Applier 用于构建树的策略。

UiApplier 采用自下而上的方式插入节点。这是为了避免新节点进入树时出现重复通知

这里再贴一遍前一篇中的自下而上插入策略图示以使其更清晰。

Jetpack Compose 深入探索系列四: Compose UI,Jetpack Compose,android,Jetpack Compose,Compose UI

自下而上构建树的方式是先将 AC 插入 B,然后将 B 树插入 R 中,从而完成树的构建。这意味着每次插入新节点时,只通知直接父节点。这对于 Android UI(也就是 UiApplier)特别有意义,因为我们通常有很多嵌套(特别是在 Compose UI 中,过度绘制不是问题文章来源地址https://www.toymoban.com/news/detail-529096.html

到了这里,关于Jetpack Compose 深入探索系列四: Compose UI的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android Jetpack组件库(第七部分)---UI工具包 Compose

    Android Jetpack 是 Google 推出的一整套帮助 Android 应用程序开发的库、工具包和架构指南,旨在为 Android 应用程序提供更快,更轻松,更稳定的开发体验。自推出以来已经发展成了一个庞大的技术生态系统,包括了许多使用方便、功能强大的库,以下是其中一些新特性、新组件:

    2024年01月16日
    浏览(37)
  • Android Jetpack Compose 用计时器demo理解Compose UI 更新的关键-------状态管理(State)

    我们都知道了Compose使用了声明式的开发范式,在这样的范式中,UI的职责更加的单一,只会对数据状态的变化作出反应,如果数据状态没有发生变化,则UI就永远不会自行的改变。假如我们把Composable的执行看成是一个函数的运算的话,那么状态就是函数的参数,输出就是生成

    2024年02月09日
    浏览(37)
  • Android Jetpack Compose 用计数器demo理解Compose UI 更新的关键-------状态管理(State)

    我们都知道了Compose使用了声明式的开发范式,在这样的范式中,UI的职责更加的单一,只会对数据状态的变化作出反应,如果数据状态没有发生变化,则UI就永远不会自行的改变。假如我们把Composable的执行看成是一个函数的运算的话,那么状态就是函数的参数,输出就是生成

    2024年02月08日
    浏览(33)
  • Android开发中的前五个代码异味:Jetpack Compose UI和MVVM

    代码异味是指软件代码中潜在问题的指标,可能并不一定是错误,但可能会导致问题,如增加维护复杂性、降低性能或降低可读性。我们将探讨Android开发中的前五个代码异味,其中包括使用Jetpack Compose UI和Model-View-ViewModel(MVVM)架构的示例。 上帝对象或上帝类是指试图做太

    2024年02月02日
    浏览(29)
  • Jetpack Compose 中的动态加载、插件化技术探索

    在传统的 Android 开发模式中,由于界面过分依赖于 Activity 、 Fragment 这样的组件,一个业务模块中往往会存在着大量的 Activity 类,因此诞生了很多的插件化框架,这些插件化框架基本都是想方设法的使用各种Hook/反射手段来解决使用未注册的组件问题。在进入 Jetpack Compose 的世

    2024年02月14日
    浏览(26)
  • Jetpack Compose UI架构

    Jetpack Compose是我职业生涯中最激动人心的事。它改变了我工作和问题思考的方式,引入了易用且灵活的工具,几乎可轻松实现各种功能。 早期在生产项目中尝试了Jetpack Compose后,我迅速着迷。尽管我已有使用Compose创建UI的经验,但对新的Jetpack Compose驱动特性的组织和架构引发

    2024年02月11日
    浏览(37)
  • Jetpack Compose -> 声明式UI & Modifier

    本章主要介绍下 Compose 的声明式 UI 以及初级写法; 传统UI 传统 UI 方式来声明UI 是通过 xml 来进行显示的,显示文字的方式是使用 TextView,它内部显示文字的方式有两种,一种是在 xml 中直接设置,通过下面这种方式设置 这种方式是通过初始值在 xml 中进行预设置的; 还有一

    2024年02月02日
    浏览(34)
  • Jetpack Compose 不止是一个UI框架~

    Jetpack Compose是用于构建原生Android UI的现代工具包。 Jetpack Compose使用更少的代码,强大的工具和直观的Kotlin API,简化并加速了Android上的UI开发。这是Android Developers 官网对它的描述。 本文不是教你Jetpack Compose 的一些基本使用方法,而是为啥我们需要Jetpack Compose 的一些简洁,让

    2024年02月03日
    浏览(35)
  • Jetpack Compose UI 底部弹窗实现

    使用Compose Ui的Dialog 默认是居中显示屏幕中间。 实现思路: 1.弹窗完全自定义一个全屏弹窗; 2.显示内容显示在底部区域。 3.点击其他空白区域关闭弹窗。

    2024年02月11日
    浏览(37)
  • Android Jetpack Compose 别裁

    一、简介 二、compose优缺点 三、compose好学吗 四、Android Jetpack Compose 跟 fluter 哪个更好 五、Android Jetpack Compose 跟 fluter 技能学习选择 之所以叫Android Jetpack Compose别裁是希望能取舍网上的对compose的资料,推出别出心裁的文章,文章结束都会有一个案例,通过实践学习,让学习的

    2024年02月03日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包