【问题标题】:Android Jetpack Compose - Dynamic layoutsAndroid Jetpack Compose - 动态布局
【发布时间】:2021-06-21 14:18:36
【问题描述】:

背景

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

项目的一部分将使用服务器驱动的 UI。然而,用户界面的变化并不能提前知道,并且将是动态的(服务器和数据驱动的)。

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

TL;DR

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

作为一个最小的例子,使用传统的ViewViewGroup 这很容易实现:

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)
    }
}

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

更新

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

话虽如此,我很快意识到像 ScrollViewView::tooltipText 这样简单的东西在 Compose 中还不存在。与 xml 布局/资源相比,基于运行时配置(屏幕方向/屏幕桶大小等)的布局也没有简单的方法。这对我来说意味着,将数据绑定与所有丰富的 View 框架和库一起使用仍然是更好的解决方案。

期待 Compose 库的更新,也许在未来的某个时间点。

【问题讨论】:

  • 嗯,将垂直的LinearLayout 替换为Column(),并将getChildren() 替换为对可组合的调用。 IOW,就您的示例而言,几乎没有什么可做的,因为您将所有复杂性都隐藏在 getChildren(someArgs) 中。
  • 我以前从未使用过compose。对于我的用例来说,这可能是一个基本问题。在近一周没有 cmets/answers/upvotes/downvotes 之后,我决定赌一把来打开赏金。我想这可能是冷漠,有限的社区构成曝光或可能是一个问得不好的问题。我会看看你的建议谢谢。
  • “我从来没有用过compose”——归根结底,Compose 就是所有功能。因此,如果有的话,服务器定义的 UI 更容易,因为您不需要处理膨胀布局或处理小部件和容器的时髦组装。使用服务器定义的 UI,您只是碰巧根据从服务器获取的内容调用函数。
  • 当然,我知道实际上我正在连接到一个在后台使用 vue.js 的封闭源代码框架。在没有接触过声明性 UI 和 Compose 的情况下,我要问的是,对我而言,关于使用 compose 创建即时 UI 需要什么的逻辑问题。我见过的所有“涉足”的例子都是已知的 UI 提前 - 我必须处理在线/离线以及曲线球。我有一些想法可以根据您的提示创建某种工厂模式,以返回更高阶的函数,这些函数是可以调用的组合,不确定是否可能,但我会试一试
  • “我在问,对我而言,关于使用 compose 创建即时 UI 需要什么的逻辑问题”——您的最终问题(“您如何使用 compose Jetpack Compose?") 与一个与服务器定义的 UI 没有明显关系的代码示例相关联。例如,您可以有一个示例,该示例显示 when 基于某些服务器提供的对象分支到不同的 UI 构造路径,这些路径构造视图。然后有人可以向您展示 when 分支到基于某些服务器提供的对象的不同 UI 构造路径,这些路径称为可组合。

标签: android android-layout android-jetpack-compose


【解决方案1】:

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

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

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

[
  {
    "element": "label",
    "attributes": {
      // values omitted for brevity
    }
  },
  {
    "element": "field",
    "attributes": {
      // values omitted for brevity
    }
  },
  // additional elements omitted for brevity
]

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

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


View-space 中,您可以有一个函数根据提供的UiElement 创建View

fun buildView(element: UiElement) = when (element) {
    is LabelElement -> buildTextView(element)
    is FieldElement -> buildEditText(element)
    else -> TODO("add other element cases here")
}

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

在您问题中的代码 sn-p 中,而不是您的 getChildren()-and-loop 方法,您将遍历 List&lt;UiElement&gt; 并为列表中的每个 UiElement 调用 buildView(),并添加结果到你的LinearLayout


Compose 等效项是这样的:

@Composable
fun buildNode(element: UiElement) {
    when (element) {
        is LabelElement -> buildTextNode(element)
        is FieldElement -> buildTextFieldNode(element)
        else -> TODO("add other element cases here")
    }
}

IOW,它几乎是一样的。主要区别在于:

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

你的活动应该是这样的:

Column {
    uiElements.forEach { buildNode(it) }
}

...代替您的LinearLayout

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


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

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

在基于 View 的 UI 和基于 Compose 的 UI(例如 JSON 解析)之间,其中一些是相同的。其中一些将大不相同,例如处理用户输入。

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

【讨论】:

  • 谢谢,这正是我想要的。我已经做过的所有“外部”范围,实际上我都在使用数据绑定、膨胀单个小部件来做所有事情——唯一的难题是 Compose 基础知识。
  • 要背负这个出色的答案,请考虑使用 Kotlin Flows 或 LiveData 在从服务器更新数据时自动响应状态。
猜你喜欢
  • 2021-07-22
  • 1970-01-01
  • 2022-08-23
  • 2022-11-06
  • 2022-07-18
  • 2021-06-03
  • 1970-01-01
  • 2021-04-15
  • 1970-01-01
相关资源
最近更新 更多