Jetpack Compose LazyColumn - 如何分别更新每个项目的值?
Jetpack Compose LazyColumn - How to update values of each Item seperately?
我正在为我的应用开发购物车功能。我正在分别查看 LazyColumn 中每个列表项的 add/decrease 数量。我只使用一个“记住”,所以如果我单击 add/decrease,它们都会同时更新。如何单独控制每个项目?
Screenshot
@Composable
fun InventoryCartScreen(
mainViewModel: MainViewModel = hiltViewModel()
) {
val multiSelectValue = mutableStateOf(0)// This is the value I want to change
//random list
val shopList = listOf(
ShoppingList(id = 0,itemNumber = "1",itemDescription = "1",currentInventory = 0,optimalInventory = 0,minInventory = 0),
ShoppingList(id = 0,itemNumber = "2",itemDescription = "2",currentInventory = 0,optimalInventory = 0,minInventory = 0)
)
Column(...) {
LazyColumn(...) {
items(items = shopList, key = { it.id }) { item ->
InventoryCartScreenContents(
onaddClick= { multiSelectValue.value ++ }, //adds for all
onDecreaseClick = { multiSelectValue.value -- }, //decreases for all
value = multiSelectValue.value //setting the initial value for all
)
}
}
}
}
以下是可帮助您重现问题的可组合内容。
@Composable
fun InventoryCartScreenContents(
onAddClick: (Int) -> Unit,
onDecreaseClick: () -> Unit,
value: Int,
) {
Row(...) {
Button(
onClick = { onAddClick(itemId) }
) {
Text(text = "+")
}
Button(
onClick = onDecreaseClick
) {
Text(text = "-")
}
}
}
在您的 ViewModel
中创建一个 mutableStateListOf(...)
(或 mutableStateOf(listOf(...))
对象,如果前者不支持您的数据类型)。现在,从您希望从中读取它的可组合项访问此状态,即您的 LazyColumn
.
在您的 ViewModel
中,您可以根据需要设置值,它们将在更新时在可组合项中更新。现在,可组合项(即您的列)可以使用列表项的索引作为惰性列项的索引。所以你可以在视图模型中为不同的项目存储不同的数据,它会工作得很好。
问题似乎是您在这里缺少 state-hoisting 的概念。我以为我有一些很好的解释它的帖子,但似乎 one's the best I've posted. Anyway, I recommend checking this official reference,因为那是我基本上发现的地方(可以这么说,有一点猎头。)
基本思想是所有内容都存储在 viewModel 中。然后,将该状态划分为“setters”和“getters”,并将它们传递给层次结构。
例如,ViewModel 可能有一个名为 text
的项,好吗?
class VM : ViewModel(){
var text by mutableStateOf("Initial Text")
private set // This ensures that it cannot be directly modified by any class other than the viewmodel
fun setText(newText: Dtring){ // Setter
text = newText
}
}
如果您希望在单击按钮时更新 text
的值,这就是您将该按钮与 viewModel
连接的方式
MainActivity{
onCreate{
setContent{
StateButton(
getter = viewModel.text, // To get the value
setter = viewModel::setText // Passing the setter directly
)
}
}
}
在您的 Button Composable 声明中
@Composable
private fun ComposeButton(
getter: String,
setter: (String) -> Unit // (receive 'String') -> return 'Unit' or 'void'
){
Button(
onClick = { setter("Updated Text") } // Calls the setter with value "Updated Text", updating the value in the viewModel
){
Text(text = getter)
}
}
此按钮从 viewModel 读取值 'get',即,它读取 text
的值,因为它被向下传递到模型到可组合项。然后,当我们收到来自按钮的点击事件(在 onClick
中)时,我们调用作为可组合项参数接收的 setter,并使用新值调用它,在此在“更新文本”的情况下,这将一直向上到 viewModel,并在其中更改 text
的值。
现在,text
最初被初始化为state-holder,在初始化时使用mutableStateOf(...)
,当它的值改变时会触发重组。现在,由于该值确实发生了变化(从“初始文本”变为“更新文本”),因此将在读取 text
变量值的所有可组合项上触发重组。现在,我们的 ComposeButton
Composable 确实读取了 text
的值,因为我们直接将它传递给该 Composable 的 getter
参数,对吗?因此,所有这些都将产生一个 Composable,它将从层次结构(视图模型)中的单个点读取值,并且仅在该值更改时更新。因此,Viewmodel 充当可组合项的单一真实来源。
当您 运行 时您会得到什么 这是一个最初显示为“初始文本”的可组合项,但当您单击它时,它会变为“更新的文本”。我们在 getter 和 setter 的帮助下将可组合项连接到主 viewModel。当可组合项接收到事件时,我们调用从模型接收到的 setters,那些 setters 继续链到 viewModel,并更改内部变量 (state-holder) 的值该模型。然后,可组合项已经通过 'getters' 读取这些变量,因此,它们会重组以反映更新后的值。这就是 state-hoisting 的意思。所有状态都存储在 viewModel 中,并向下传递给可组合项。当一个值需要更改时,可组合项将 'events' 向上传递给视图模型(在层次结构中向上),然后在更新值时,更新后的状态向下传递给可组合项('state' 流降低层次结构)。
您所需要的只是使用此方法,但要有一个列表。您可以通过使用它们的索引来跟踪项目,并在 viewModel 中更新它们的属性,就像这个示例演示更新 a
的值一样。您可以将一个项目的所有属性存储在一个列表中。
只需创建一个data-class,像这样
data class Item(p1: ..., p2:..., p3 : ...){}
然后,val list by mutableStateOf(listOf<Item>())
清除了吗?
好的,这是针对您的 use-case 的解释。
您的代码似乎过大,但这是我得到的:
lazycolumn 中有两个项目,每个项目都有三个按钮。你的问题是关于两个按钮,增加和减少。您希望按钮只修改它们所属项目的属性,对吗?
好的,再次使用 state-hoisting,如上所述
ViewModel{
val list by mutableStateOf(listOf<Item>()) // Main (central) list
val updateItem(itemIndex: Int, item: Item){
list.set(itemIndex, item) // sets the element at 'itemIndex' to 'item'
} // You can fill the list with initial values if you like, for testing
}
正在创建 Getters 和 Setters,您将使用它们来读取和更新整个项目,即使您必须修改其中的单个 属性。我们可以使用 copy()
这样的便利方法来让我们的生活更轻松。
MainScreen(){
//Assuming here is the LazyColumn
list = viewModel.list // Get the central list here
LazyColumn(...){ // Try to minimize code like this in your questions, since it does not have to compile, just an illustration.
items(list){ item ->
// Ditch the multiSelectValue, we have the state in the model now.
Item( // A Composable, like you demonstrated, but with a simpler name for convenience
item: Item,
onItemUpdate: (Item) -> Unit // You don't need anything else
)
}
}
}
只需创建一个数据 class 并将所有内容存储在其中。我演示的 class Item
(不是可组合项)是 data-class。它可能有这样的值
data class Item(var quantity: Int, ...){} // We will increase/decrease this
现在,在您收到 'add' 事件的 Item
可组合项中(在 'Add' 按钮的 onClick
中),只需使用setter 像这样
onClick = {
updateItem(
item // Getter
.copy( //Convenience Method
quantity = item.quantity + 1 // Increase current Quantity
)
)
}
减少和删除(updateItem(0)
),你应该做的很好......最后。如果您还有任何疑问,请询问。我可以协助完成如果没有任何效果,时间。
根据@MARSK 的回答,我已经成功实现了目标(谢谢!)
添加更新项目值的函数:
//Creating a function to update a certain item with a new value
fun updateShoppingItem(shoppingItem: ShoppingList, newValue: Int) {
val newInventoryMultiValue = shoppingItem.copy(currentInventory = shoppingItem.currentInventory.plus(newValue))
updateShoppingList(newInventoryMultiValue)
}
//Actually updating the room item with the function above
private fun updateShoppingList(shoppingItem: ShoppingList) {
viewModelScope.launch {
repository.updateShoppingItem(shoppingItem)
}
}
然后,在 Composable 屏幕中将功能添加到添加和减少按钮
val shoppingList by mainViewModel.getShoppingList.collectAsState(initial = emptyList())
LazyColumn() {
items(items = shoppingList)
{ item ->
InventoryCartScreenContents(
onAddClick = {
val newValue = 1
mainViewModel.updateShoppingItem(item, newValue)
},
onDecreaseClick = {
val newValue = -1
mainViewModel.updateShoppingItem(item, newValue)
},
}
)
}
}
}
Result
我正在为我的应用开发购物车功能。我正在分别查看 LazyColumn 中每个列表项的 add/decrease 数量。我只使用一个“记住”,所以如果我单击 add/decrease,它们都会同时更新。如何单独控制每个项目?
Screenshot
@Composable
fun InventoryCartScreen(
mainViewModel: MainViewModel = hiltViewModel()
) {
val multiSelectValue = mutableStateOf(0)// This is the value I want to change
//random list
val shopList = listOf(
ShoppingList(id = 0,itemNumber = "1",itemDescription = "1",currentInventory = 0,optimalInventory = 0,minInventory = 0),
ShoppingList(id = 0,itemNumber = "2",itemDescription = "2",currentInventory = 0,optimalInventory = 0,minInventory = 0)
)
Column(...) {
LazyColumn(...) {
items(items = shopList, key = { it.id }) { item ->
InventoryCartScreenContents(
onaddClick= { multiSelectValue.value ++ }, //adds for all
onDecreaseClick = { multiSelectValue.value -- }, //decreases for all
value = multiSelectValue.value //setting the initial value for all
)
}
}
}
}
以下是可帮助您重现问题的可组合内容。
@Composable
fun InventoryCartScreenContents(
onAddClick: (Int) -> Unit,
onDecreaseClick: () -> Unit,
value: Int,
) {
Row(...) {
Button(
onClick = { onAddClick(itemId) }
) {
Text(text = "+")
}
Button(
onClick = onDecreaseClick
) {
Text(text = "-")
}
}
}
在您的 ViewModel
中创建一个 mutableStateListOf(...)
(或 mutableStateOf(listOf(...))
对象,如果前者不支持您的数据类型)。现在,从您希望从中读取它的可组合项访问此状态,即您的 LazyColumn
.
在您的 ViewModel
中,您可以根据需要设置值,它们将在更新时在可组合项中更新。现在,可组合项(即您的列)可以使用列表项的索引作为惰性列项的索引。所以你可以在视图模型中为不同的项目存储不同的数据,它会工作得很好。
问题似乎是您在这里缺少 state-hoisting 的概念。我以为我有一些很好的解释它的帖子,但似乎
基本思想是所有内容都存储在 viewModel 中。然后,将该状态划分为“setters”和“getters”,并将它们传递给层次结构。
例如,ViewModel 可能有一个名为 text
的项,好吗?
class VM : ViewModel(){
var text by mutableStateOf("Initial Text")
private set // This ensures that it cannot be directly modified by any class other than the viewmodel
fun setText(newText: Dtring){ // Setter
text = newText
}
}
如果您希望在单击按钮时更新 text
的值,这就是您将该按钮与 viewModel
MainActivity{
onCreate{
setContent{
StateButton(
getter = viewModel.text, // To get the value
setter = viewModel::setText // Passing the setter directly
)
}
}
}
在您的 Button Composable 声明中
@Composable
private fun ComposeButton(
getter: String,
setter: (String) -> Unit // (receive 'String') -> return 'Unit' or 'void'
){
Button(
onClick = { setter("Updated Text") } // Calls the setter with value "Updated Text", updating the value in the viewModel
){
Text(text = getter)
}
}
此按钮从 viewModel 读取值 'get',即,它读取 text
的值,因为它被向下传递到模型到可组合项。然后,当我们收到来自按钮的点击事件(在 onClick
中)时,我们调用作为可组合项参数接收的 setter,并使用新值调用它,在此在“更新文本”的情况下,这将一直向上到 viewModel,并在其中更改 text
的值。
现在,text
最初被初始化为state-holder,在初始化时使用mutableStateOf(...)
,当它的值改变时会触发重组。现在,由于该值确实发生了变化(从“初始文本”变为“更新文本”),因此将在读取 text
变量值的所有可组合项上触发重组。现在,我们的 ComposeButton
Composable 确实读取了 text
的值,因为我们直接将它传递给该 Composable 的 getter
参数,对吗?因此,所有这些都将产生一个 Composable,它将从层次结构(视图模型)中的单个点读取值,并且仅在该值更改时更新。因此,Viewmodel 充当可组合项的单一真实来源。
当您 运行 时您会得到什么 这是一个最初显示为“初始文本”的可组合项,但当您单击它时,它会变为“更新的文本”。我们在 getter 和 setter 的帮助下将可组合项连接到主 viewModel。当可组合项接收到事件时,我们调用从模型接收到的 setters,那些 setters 继续链到 viewModel,并更改内部变量 (state-holder) 的值该模型。然后,可组合项已经通过 'getters' 读取这些变量,因此,它们会重组以反映更新后的值。这就是 state-hoisting 的意思。所有状态都存储在 viewModel 中,并向下传递给可组合项。当一个值需要更改时,可组合项将 'events' 向上传递给视图模型(在层次结构中向上),然后在更新值时,更新后的状态向下传递给可组合项('state' 流降低层次结构)。
您所需要的只是使用此方法,但要有一个列表。您可以通过使用它们的索引来跟踪项目,并在 viewModel 中更新它们的属性,就像这个示例演示更新 a
的值一样。您可以将一个项目的所有属性存储在一个列表中。
只需创建一个data-class,像这样
data class Item(p1: ..., p2:..., p3 : ...){}
然后,val list by mutableStateOf(listOf<Item>())
清除了吗?
好的,这是针对您的 use-case 的解释。
您的代码似乎过大,但这是我得到的:
lazycolumn 中有两个项目,每个项目都有三个按钮。你的问题是关于两个按钮,增加和减少。您希望按钮只修改它们所属项目的属性,对吗?
好的,再次使用 state-hoisting,如上所述
ViewModel{
val list by mutableStateOf(listOf<Item>()) // Main (central) list
val updateItem(itemIndex: Int, item: Item){
list.set(itemIndex, item) // sets the element at 'itemIndex' to 'item'
} // You can fill the list with initial values if you like, for testing
}
正在创建 Getters 和 Setters,您将使用它们来读取和更新整个项目,即使您必须修改其中的单个 属性。我们可以使用 copy()
这样的便利方法来让我们的生活更轻松。
MainScreen(){
//Assuming here is the LazyColumn
list = viewModel.list // Get the central list here
LazyColumn(...){ // Try to minimize code like this in your questions, since it does not have to compile, just an illustration.
items(list){ item ->
// Ditch the multiSelectValue, we have the state in the model now.
Item( // A Composable, like you demonstrated, but with a simpler name for convenience
item: Item,
onItemUpdate: (Item) -> Unit // You don't need anything else
)
}
}
}
只需创建一个数据 class 并将所有内容存储在其中。我演示的 class Item
(不是可组合项)是 data-class。它可能有这样的值
data class Item(var quantity: Int, ...){} // We will increase/decrease this
现在,在您收到 'add' 事件的 Item
可组合项中(在 'Add' 按钮的 onClick
中),只需使用setter 像这样
onClick = {
updateItem(
item // Getter
.copy( //Convenience Method
quantity = item.quantity + 1 // Increase current Quantity
)
)
}
减少和删除(updateItem(0)
),你应该做的很好......最后。如果您还有任何疑问,请询问。我可以协助完成如果没有任何效果,时间。
根据@MARSK 的回答,我已经成功实现了目标(谢谢!)
添加更新项目值的函数:
//Creating a function to update a certain item with a new value
fun updateShoppingItem(shoppingItem: ShoppingList, newValue: Int) {
val newInventoryMultiValue = shoppingItem.copy(currentInventory = shoppingItem.currentInventory.plus(newValue))
updateShoppingList(newInventoryMultiValue)
}
//Actually updating the room item with the function above
private fun updateShoppingList(shoppingItem: ShoppingList) {
viewModelScope.launch {
repository.updateShoppingItem(shoppingItem)
}
}
然后,在 Composable 屏幕中将功能添加到添加和减少按钮
val shoppingList by mainViewModel.getShoppingList.collectAsState(initial = emptyList())
LazyColumn() {
items(items = shoppingList)
{ item ->
InventoryCartScreenContents(
onAddClick = {
val newValue = 1
mainViewModel.updateShoppingItem(item, newValue)
},
onDecreaseClick = {
val newValue = -1
mainViewModel.updateShoppingItem(item, newValue)
},
}
)
}
}
}
Result