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