2

我有一个带有一些可变状态列表的组件。我将其中的一个项目以及删除该项目的回调传递给另一个组件。

@Composable
fun MyApp() {
  val myItems = mutableStateListOf("1", "2", "3")
  LazyColumn {
    items(myItems) { item ->
      MyComponent(item) { toDel -> myItems.remove(toDel) }
    }
  }
}

组件在修饰符中调用delete回调。clickable

@Composable
fun MyComponent(item: String, delete: (String) -> Unit = {}) {
  Column {
    Box(
      Modifier
        .size(200.dp)
        .background(MaterialTheme.colors.primary)
        .clickable { delete(item) }
    ) {
      Text(item, fontSize = 40.sp)
    }
  }
}

这工作正常。但是当我clickable为我自己的修改器更改时,pointerInput()就会出现问题。

fun Modifier.myClickable(delete: () -> Unit) =
  pointerInput(Unit) {
    awaitPointerEventScope { awaitFirstDown() }
    delete()
  }

@Composable
fun MyComponent(item: String, delete: (String) -> Unit = {}) {
  Column {
    Box(
      Modifier
        .size(200.dp)
        .background(MaterialTheme.colors.primary)
        .myClickable { delete(item) } // NEW
    ) {
      Text(item, fontSize = 40.sp)
    }
  }
}

如果我单击第一项,它将删除它。接下来,如果我单击最新的顶部项目,则会调用现在已删除的第一项的旧回调,尽管旧组件已被删除。

我不知道为什么会这样。但我可以修复它。我使用key()

@Composable
fun MyApp() {
  val myItems = mutableStateListOf("1", "2", "3")
  LazyColumn {
    items(myItems) { item ->
      key(item) { // NEW
        MyComponent(item) { toDel -> myItems.remove(toDel) }
      }
    }
  }
}

key()那么,当我使用自己的修饰符时,为什么需要?jetpack的这段代码也是这种情况,我不知道为什么。


正如接受的答案所说,Compose 不会重新计算我的自定义修饰符,因为pointerEvent()没有唯一键。

fun Modifier.myClickable(key:Any? = null, delete: () -> Unit) =
  pointerInput(key) {
    awaitPointerEventScope { awaitFirstDown() }
    delete()
  }

    Box(
      Modifier
        .size(200.dp)
        .background(MaterialTheme.colors.primary)
        .myClickable(key = item) { delete(item) } // NEW
    ) {
      Text(item, fontSize = 40.sp)
    }

修复它,我不需要key()在外部组件中使用。但是,我仍然不确定为什么我不需要向 发送唯一密钥clickable {}

4

2 回答 2

2

Compose 正在尝试通过使用键本地化范围来缓存尽可能多的工作:当它们自上次运行以来没有更改时 - 我们正在使用缓存值,否则我们需要重新计算它。

通过设置key惰性项,您可以定义remember内部所有计算的范围,并且许多系统功能都是使用它实现的,remember因此变化很大。项目索引是惰性项目中的默认键

因此,在您删除 first 之后item,第一个惰性项会以与以前相同的上下文重用

现在我们来到您的myClickable. 你Unit作为一个key进入pointerInput(它也有一个remember内部)。通过这样做,您对重构器说:永远不要重新计算这个值,直到上下文发生变化。并且第一个惰性项的上下文没有改变,例如key仍然是相同的索引,这就是为什么 lambda with removeditem仍然缓存在该函数中

当您指定惰性项key等于 时item,您也在更改所有惰性项的上下文,因此pointerInput会重新计算。如果你通过你的item而不是Unit你会有同样的效果

因此,当您需要使用key时,您需要使用您的计算不会以不好的方式缓存在惰性项之间

在文档中查看有关惰性列键的更多信息

于 2021-08-15T18:03:30.407 回答
1

Jetpack compose 通过仅重新组合值已更改的 Widget 来优化重新组合。

Modifier.myClickable在项目列表因删除而发生更改时的自定义实现中,Text(item, fontSize = 40.sp)由于项目已更改并且它是唯一正在读取的项目,因此只会重新组合内部item。外部 Box()没有被重组,因此它持有之前的回调。但是当你添加key(item)时,外框也会随着键值的变化而重新组合。因此,它在添加密钥后工作。

那么为什么要与 合作Modifier.clickable { delete(item) }

我认为 Compose 会跟踪回调中的变化clickable { delete(item) }。因此,当回调因项目删除而改变时,它会重组MyComponent,因此正在使用clickable

于 2021-08-15T17:57:57.477 回答