Android Jetpack Compose - 动态布局
Android Jetpack Compose - Dynamic layouts
背景
我目前正在研究创建布局的选项,在项目开发期间,我希望有可能将 UI 迁移到 Jetpack Compose 或 post 版本,具体取决于 [=图书馆的 47=]。
部分项目将使用服务器驱动 UI。然而,UI 的变化无法提前知晓,并且将是动态的(服务器和数据驱动)。
我在处理业务逻辑和表示层方面没有问题,但是当涉及到 UI 时,我需要根据表示数据和视图模型动态构建 UI .
TL;DR
考虑到这一点,是否可以使用 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 layouts/resources 相比,也没有简单的方法可以根据运行时配置(屏幕方向/屏幕桶大小等)进行布局。这意味着对我来说,将数据绑定与所有丰富的 View
框架和库一起使用仍然是更好的解决方案。
期待 Compose 库的更新,也许在未来的某个时候看看。
With this in mind is it possible to create dynamic layouts (not to be confused with dynamic layout data) using Jetpack Compose?
当然可以。 Compose 是所有函数。您可以根据该数据解析数据并调用函数,无论该数据是“填充此预定义的UI结构”还是该数据是“定义UI结构”。
例如,假设您的服务器有一个端点 return 如下 JSON:
[
{
"element": "label",
"attributes": {
// values omitted for brevity
}
},
{
"element": "field",
"attributes": {
// values omitted for brevity
}
},
// additional elements omitted for brevity
]
你的工作是 assemble 基于 JSON 的 UI。 label
元素应该是固定文本,field
元素应该是文本输入字段,等等。 attributes
对象包含因元素而异的详细信息。
所以,你解析它。假设结果是 List<UiElement>
,其中 UiElement
是接口或抽象 class 之类的东西,子类型基于支持的元素(例如 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()
将 assemble 变成 TextView
,无论是从布局中膨胀还是调用构造函数。 buildEditText()
将 assemble 变成 EditText
,无论是从布局中膨胀还是调用构造函数。等等。这些函数中的每一个都将负责从 attributes
中获取值并用它们做一些有用的事情,例如在 TextView
中设置文本或在 EditText
中设置提示。
在您问题的代码片段中,而不是您的 getChildren()
循环方法,您将迭代 List<UiElement>
并为每个 [=18= 调用 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()
也需要)
- 不需要 return 任何东西,因为可组合项会自动添加到父项中
buildTextNode()
和 buildTextFieldNode()
中的详细信息会让人联想到 buildTextView()
和 buildEditText()
,但基于可组合项
你的 activity 会是这样的:
Column {
uiElements.forEach { buildNode(it) }
}
...作为您的 LinearLayout
.
的替代品
(实际上,这两个示例都需要一个滚动容器,但我们在这里也将忽略它)
服务器定义的所有复杂性 UI 都超出了您的代码示例的范围:
- 如何解析服务器响应?
- 如何将该服务器响应映射到表示所需 UI 的对象模型?
- 如何让我的每个元素 UI 位工作?
- 如何处理事件侦听器?
- 更一般地说,我们正在做什么来响应用户对此 UI 的输入?
- 我们如何根据需要重新生成此 UI(对于视图,基于配置更改;对于可组合项,基于重组)?
- 以此类推
其中一些在基于 View
的 UI 和基于 Compose 的 UI 之间是相同的——例如,JSON 解析。其中一些会有很大的不同,例如处理用户输入。
但是“解析服务器响应并根据该响应创建 UI 元素”的一般方法,视图和可组合项同样可以应对挑战。特别是,在您问题的代码示例级别,视图和可组合项都可以处理您的高级场景。细节决定成败。
背景
我目前正在研究创建布局的选项,在项目开发期间,我希望有可能将 UI 迁移到 Jetpack Compose 或 post 版本,具体取决于 [=图书馆的 47=]。
部分项目将使用服务器驱动 UI。然而,UI 的变化无法提前知晓,并且将是动态的(服务器和数据驱动)。
我在处理业务逻辑和表示层方面没有问题,但是当涉及到 UI 时,我需要根据表示数据和视图模型动态构建 UI .
TL;DR
考虑到这一点,是否可以使用 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 layouts/resources 相比,也没有简单的方法可以根据运行时配置(屏幕方向/屏幕桶大小等)进行布局。这意味着对我来说,将数据绑定与所有丰富的 View
框架和库一起使用仍然是更好的解决方案。
期待 Compose 库的更新,也许在未来的某个时候看看。
With this in mind is it possible to create dynamic layouts (not to be confused with dynamic layout data) using Jetpack Compose?
当然可以。 Compose 是所有函数。您可以根据该数据解析数据并调用函数,无论该数据是“填充此预定义的UI结构”还是该数据是“定义UI结构”。
例如,假设您的服务器有一个端点 return 如下 JSON:
[
{
"element": "label",
"attributes": {
// values omitted for brevity
}
},
{
"element": "field",
"attributes": {
// values omitted for brevity
}
},
// additional elements omitted for brevity
]
你的工作是 assemble 基于 JSON 的 UI。 label
元素应该是固定文本,field
元素应该是文本输入字段,等等。 attributes
对象包含因元素而异的详细信息。
所以,你解析它。假设结果是 List<UiElement>
,其中 UiElement
是接口或抽象 class 之类的东西,子类型基于支持的元素(例如 LabelElement
, FieldElement
).现在你的工作是根据 List<UiElement>
.
在 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()
将 assemble 变成 TextView
,无论是从布局中膨胀还是调用构造函数。 buildEditText()
将 assemble 变成 EditText
,无论是从布局中膨胀还是调用构造函数。等等。这些函数中的每一个都将负责从 attributes
中获取值并用它们做一些有用的事情,例如在 TextView
中设置文本或在 EditText
中设置提示。
在您问题的代码片段中,而不是您的 getChildren()
循环方法,您将迭代 List<UiElement>
并为每个 [=18= 调用 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()
也需要)- 不需要 return 任何东西,因为可组合项会自动添加到父项中
buildTextNode()
和buildTextFieldNode()
中的详细信息会让人联想到buildTextView()
和buildEditText()
,但基于可组合项
你的 activity 会是这样的:
Column {
uiElements.forEach { buildNode(it) }
}
...作为您的 LinearLayout
.
(实际上,这两个示例都需要一个滚动容器,但我们在这里也将忽略它)
服务器定义的所有复杂性 UI 都超出了您的代码示例的范围:
- 如何解析服务器响应?
- 如何将该服务器响应映射到表示所需 UI 的对象模型?
- 如何让我的每个元素 UI 位工作?
- 如何处理事件侦听器?
- 更一般地说,我们正在做什么来响应用户对此 UI 的输入?
- 我们如何根据需要重新生成此 UI(对于视图,基于配置更改;对于可组合项,基于重组)?
- 以此类推
其中一些在基于 View
的 UI 和基于 Compose 的 UI 之间是相同的——例如,JSON 解析。其中一些会有很大的不同,例如处理用户输入。
但是“解析服务器响应并根据该响应创建 UI 元素”的一般方法,视图和可组合项同样可以应对挑战。特别是,在您问题的代码示例级别,视图和可组合项都可以处理您的高级场景。细节决定成败。