Android Jetpack Compose - 动态布局

Mar*_*een 3 android android-layout android-jetpack-compose

背景

我目前正在研究创建布局的选项,在项目开发过程中,我希望可能将 UI 迁移到 Jetpack Compose 或发布后,具体取决于库的稳定性/灵活性。

该项目的一部分将使用服务器驱动的 UI。然而,UI 的变化是无法提前得知的,并且将是动态的(服务器和数据驱动)。

我在处理业务逻辑和表示层方面没有任何问题,但是当涉及到 UI 时,我需要根据表示数据和视图模型动态构建 UI。

长话短说

考虑到这一点,是否可以使用 Jetpack Compose 创建动态布局(不要与动态布局数据混淆)?

作为一个最小的例子,使用传统的View方法ViewGroup可以轻松实现:

class DynamicViewActivity : AppCompatActivity() {
    
    private lateinit var root : LinearLayout

    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)

        // setup view group container
        root = LinearLayout(this)
        root.orientation = LinearLayout.VERTICAL
        root.layoutParams = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT, 
            LinearLayout.LayoutParams.MATCH_PARENT)
        
        setContentView(root, LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT))
        
        // some lookup to create a dynamic layout
        val children : List<Pair<View, LinearLayout.LayoutParams>> = getChildren(someArgs)
        
        // add child views
        children.forEach { (view, params) -> root.addView(view, params) }
    }
    
    fun <T : View> addViewToRoot(view: T, params: LinearLayout.LayoutParams) {
        root.addView(view, params)
    }

    fun  removeFromRoot(viewTag : String) {
        root.findViewWithTag<View>(viewTag)?.let(root::removeView)
    }
}
Run Code Online (Sandbox Code Playgroud)

如何使用 Jetpack Compose 做同样的事情?

更新

根据 @CommonsWare 的回答,我在 Compose 中实现了 UI。由于我的实际代码有一个非常薄的 UI 层,所有侦听器和事件都使用单向和双向数据绑定,并且答案中的“未知”已在我的项目中解决,因此交换 UI 非常容易。

话虽如此,我很快意识到Compose 中尚不存在诸如ScrollView和 之类的简单事物。View::tooltipText与 xml 布局/资源相比,也没有简单的方法可以根据运行时配置(屏幕方向/屏幕桶大小等)进行布局。这对我来说意味着,使用数据绑定与所有丰富的View框架和库仍然是更好的解决方案。

期待 Compose 库更新,也许会在未来的某个时候关注。

Com*_*are 12

\n

考虑到这一点,是否可以使用 Jetpack Compose 创建动态布局(不要与动态布局数据混淆)?

\n
\n

当然。撰写是所有功能。您可以解析数据并根据该数据调用函数,无论该数据是“填充此预定义的 UI 结构”还是该数据是“定义 UI 结构”。

\n

例如,假设您的服务器有一个返回以下 JSON 的端点:

\n
[\n  {\n    "element": "label",\n    "attributes": {\n      // values omitted for brevity\n    }\n  },\n  {\n    "element": "field",\n    "attributes": {\n      // values omitted for brevity\n    }\n  },\n  // additional elements omitted for brevity\n]\n
Run Code Online (Sandbox Code Playgroud)\n

您的工作是根据该 JSON 组装一个 UI。对于各种类型,元素label应该是固定文本,field元素应该是文本输入字段等等。该attributes对象包含因元素而异的详细信息。

\n

所以,你解析一下。假设您最终得到一个List<UiElement>结果,其中UiElement是一个接口或抽象类或其他东西,其子类型基于受支持的元素(例如,LabelElementFieldElement)。现在您的工作是基于此构建一个 UI List<UiElement>

\n
\n

在-space 中,您可以拥有一个基于提供的View创建 的函数:ViewUiElement

\n
fun buildView(element: UiElement) = when (element) {\n    is LabelElement -> buildTextView(element)\n    is FieldElement -> buildEditText(element)\n    else -> TODO("add other element cases here")\n}\n
Run Code Online (Sandbox Code Playgroud)\n

buildTextView()会组装 a TextView,无论是从布局膨胀还是调用构造函数。buildEditText()会组装一个EditText,无论是从布局膨胀还是调用构造函数。等等。这些函数中的每一个都负责从 中获取值attributes并用它们做一些有用的事情,例如在 a 中设置文本TextView或在 中设置提示EditText

\n

在问题的代码片段中,getChildren()您将迭代List<UiElement>并调用列表中的buildView()每个,而不是使用 -and-loop 方法UiElement,并将结果添加到您的LinearLayout.

\n
\n

等效的 Compose 是这样的:

\n
@Composable\nfun buildNode(element: UiElement) {\n    when (element) {\n        is LabelElement -> buildTextNode(element)\n        is FieldElement -> buildTextFieldNode(element)\n        else -> TODO("add other element cases here")\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

IOW,它几乎是相同的。主要区别是:

\n
    \n
  • 注释(和@Composable上也需要)buildTextNode()buildTextFieldNode()
  • \n
  • 无需返回任何内容,因为可组合项会自动添加到父级
  • \n
  • buildTextNode()and中内容的细节buildTextFieldNode()会让人想起buildTextView()and buildEditText(),但基于可组合项
  • \n
\n

你的活动应该是这样的:

\n
Column {\n    uiElements.forEach { buildNode(it) }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

...作为您的替代品LinearLayout

\n

(实际上,这两个示例都需要一个滚动容器,但我们在这里也将忽略它)

\n
\n

服务器定义的 UI 的所有复杂性都超出了代码示例的范围:

\n
    \n
  • 如何解析服务器响应?
  • \n
  • 如何将该服务器响应映射到表示所需 UI 的对象模型中?
  • \n
  • 如何让每个元素的 UI 位发挥作用?
  • \n
  • 如何处理事件监听器?
  • \n
  • 更一般地说,我们正在做什么来响应此 UI 上的用户输入?
  • \n
  • 我们将如何根据需要重新生成此 UI(对于视图,基于配置更改;对于可组合项,基于重组)?
  • \n
  • 等等
  • \n
\n

View例如,基于 - 的 UI 和基于 Compose 的 UI \xe2\x80\x94 JSON 解析之间的某些内容是相同的。其中一些会有很大不同,例如处理用户输入。

\n

但“解析服务器响应并根据该响应创建 UI 元素”的一般方法、视图和可组合项同样能够应对挑战。特别是,在您的问题中的代码示例级别,视图和可组合项都可以处理您的高级场景。细节决定成败。

\n