[英]Android Jetpack Compose - Dynamic layouts
背景
我目前正在研究创建布局的选项,在项目开发期间,我希望根据库的稳定性/灵活性将 UI 迁移到 Jetpack Compose 或发布后。
该项目的一部分将使用服务器驱动的 UI。 然而,UI 的扭曲不是提前知道的,而是动态的(服务器和数据驱动)。
我在处理业务逻辑和表示层方面没有问题,但是当涉及到 UI 时,我需要根据表示数据和视图模型动态构建 UI。
TL; 博士
考虑到这一点,是否可以使用 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)
}
}
您如何使用 Jetpack Compose 做同样的事情?
更新
按照@CommonsWare 的回答,我在 Compose 中实现了 UI。 由于我的实际代码有一个非常薄的 UI 层,所有侦听器和事件都使用单向和双向数据绑定,并且我的项目中已经解决了答案中的“未知”,因此交换 UI 非常容易。
话虽如此,我很快意识到像ScrollView
和View::tooltipText
这样的简单东西在 Compose 中还不存在。 与 xml 布局/资源相比,也没有简单的方法可以根据运行时配置(屏幕方向/屏幕桶大小等)进行布局。 这意味着对我来说,将数据绑定与所有丰富的View
框架和库一起使用仍然是更好的解决方案。
期待 Compose 库的更新,也许会在未来的某个时候看看。
考虑到这一点,是否可以使用 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<UiElement>
,其中UiElement
是一个接口或抽象类或其他东西,具有基于支持的元素的子类型(例如, LabelElement
、 FieldElement
)。 现在您的工作是基于List<UiElement>
构建一个 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
设置提示。
在您问题中的代码片段中,您将遍历List<UiElement>
并为列表中的每个UiElement
调用buildView()
,并将结果添加到您的LinearLayout
,而不是您的getChildren()
-and-loop 方法。
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 的所有复杂性都超出了代码示例的范围:
其中一些在基于View
的 UI 和基于 Compose 的 UI 之间是相同的——例如 JSON 解析。 其中一些将大不相同,例如处理用户输入。
但是“解析服务器响应并基于该响应创建 UI 元素”的一般方法、视图和可组合项同样可以应对挑战。 特别是,在您问题中的代码示例级别,视图和可组合项都可以处理您的高级场景。 细节决定成败。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.