android - SwipeToDismiss 在带有动画的 LazyColumn 内

标签 android kotlin android-jetpack-compose

我正在努力实现类似 this 的目标但使用 Jetpack Compose。换句话说,滑动删除就像我们在 RecyclerView 中所做的那样。与 ItemTouchHelperclass DiffCallBack : DiffUtil.ItemCallback<RvModel>()我们在哪里可以看到enter - exit animations然后列表在插入或删除项目的位置优雅地向上或向下移动。

这是我试过的:

LazyColumn(state = listState) {
    items(products, {listItem:InventoryEntity -> listItem.inventoryId}) { item ->
        var unread by remember { mutableStateOf(false) }
        val dismissState = rememberDismissState(
            confirmStateChange = {
                if (it == DismissValue.DismissedToEnd) unread = !unread
                it != DismissValue.DismissedToEnd
            }
        )
        val isDismissed = dismissState.isDismissed(DismissDirection.EndToStart)

        if (dismissState.isDismissed(DismissDirection.EndToStart)){
            LaunchedEffect(Unit) {
                delay(300)
                viewModel.deleteProduct(item.inventoryId)
            }

        }

        var itemAppeared by remember { mutableStateOf(!columnAppeared) }
        LaunchedEffect(Unit) {
            itemAppeared = true
        }

        AnimatedVisibility(
            visible = itemAppeared && !isDismissed,
            exit = shrinkVertically(
                animationSpec = tween(
                    durationMillis = 300,
                )
            ),
            enter = expandVertically(
                animationSpec = tween(
                    durationMillis = 300
                )
            )
        ) {
            SwipeToDismiss(
                state = dismissState,
                modifier = Modifier.padding(vertical = 4.dp),
                directions = setOf(
                    DismissDirection.StartToEnd,
                    DismissDirection.EndToStart
                ),
                dismissThresholds = { direction ->
                    FractionalThreshold(if (direction == DismissDirection.StartToEnd) 0.25f else 0.5f)
                },
                background = {
                    val direction =
                        dismissState.dismissDirection ?: return@SwipeToDismiss
                    val color by animateColorAsState(
                        when (dismissState.targetValue) {
                            DismissValue.Default -> Color.LightGray
                            DismissValue.DismissedToEnd -> Color.Green
                            DismissValue.DismissedToStart -> Color.Red
                        }
                    )
                    val alignment = when (direction) {
                        DismissDirection.StartToEnd -> Alignment.CenterStart
                        DismissDirection.EndToStart -> Alignment.CenterEnd
                    }
                    val icon = when (direction) {
                        DismissDirection.StartToEnd -> Icons.Default.Done
                        DismissDirection.EndToStart -> Icons.Default.Delete
                    }
                    val scale by animateFloatAsState(
                        if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f
                    )

                    Box(
                        Modifier
                            .fillMaxSize()
                            .background(color)
                            .padding(horizontal = 20.dp),
                        contentAlignment = alignment
                    ) {
                        Icon(
                            icon,
                            contentDescription = "Localized description",
                            modifier = Modifier.scale(scale)
                        )
                    }
                },
                dismissContent = {
                    Card(
                        elevation = animateDpAsState(
                            if (dismissState.dismissDirection != null) 4.dp else 0.dp
                        ).value
                    ) {
                        ProductRow(product = item, number = item.inventoryId)
                    }
                }
            )
        }
    }
}

尽管它有效。滚动不流畅,当我向上滚动时它会跳到顶部。实现此功能的正确方法是什么?

最佳答案

最近 google 宣布了 compose 版本 1.1.0-beta03。现在我们有了一种新的方法来为项目设置动画。他们引入了一个新的修饰符:Modifier.animateItemPlacement()。您可以在 this link 中找到最新的 compose 版本.

我将尝试用最少的代码发布一个示例,以便您可以重现它并了解如何在 LazyColumn 中使用动画实现 SwipeToDismiss。

数据类存储信息:

data class DataSet(
    val itemId: Int,
    val itemName: String,
    val itemQty: String
)

比较列表项的比较器:

private val ListComparator = Comparator<DataSet> { left, right ->
    left.itemId.compareTo(right.itemId)
}

我们每个项目所在的行:

@Composable
fun ItemRow(
    modifier: Modifier = Modifier,
    product: DataSet,
    number: Int
) {

    Card(
        shape = RoundedCornerShape(4.dp),
        modifier = modifier
            .padding(8.dp)
            .fillMaxWidth(),
        backgroundColor = Color.LightGray
    ) {
        Row(modifier = modifier) {
            Text(
                text = "$number.", modifier = Modifier
                    .weight(2f)
                    .padding(start = 8.dp, end = 4.dp)
            )
            Text(
                text = product.itemName, modifier = Modifier
                    .weight(10f)
                    .padding(end = 4.dp)
            )
            Text(
                text = product.itemQty, modifier = Modifier
                    .weight(2f)
                    .padding(end = 4.dp)
            )
        }
    }
}

将所有内容组合到我们的可组合项中:

@ExperimentalMaterialApi
@ExperimentalFoundationApi
@Composable
fun helloWorld() {
    var list by remember { mutableStateOf(listOf<DataSet>()) }

    val comparator by remember { mutableStateOf(ListComparator) }

    LazyColumn {
        item {
            Button(onClick = {
                list = list + listOf(DataSet((0..1111).random(), "A random item", "100"))
            }) {
                Text("Add an item to the list")
            }
        }

        val sortedList = list.sortedWith(comparator)

        items(sortedList, key = { it.itemId }) { item ->
            val dismissState = rememberDismissState()
            if (dismissState.isDismissed(DismissDirection.EndToStart)) {
                list = list.toMutableList().also { it.remove(item) } // remove
            }
            SwipeToDismiss(
                state = dismissState,
                modifier = Modifier
                    .padding(vertical = 1.dp)
                    .animateItemPlacement(),
                directions = setOf(DismissDirection.StartToEnd, DismissDirection.EndToStart),
                dismissThresholds = { direction ->
                    FractionalThreshold(if (direction == DismissDirection.StartToEnd) 0.25f else 0.5f)
                },
                background = {
                    val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
                    val color by animateColorAsState(
                        when (dismissState.targetValue) {
                            DismissValue.Default -> Color.LightGray
                            DismissValue.DismissedToEnd -> Color.Green
                            DismissValue.DismissedToStart -> Color.Red
                        }
                    )
                    val alignment = when (direction) {
                        DismissDirection.StartToEnd -> Alignment.CenterStart
                        DismissDirection.EndToStart -> Alignment.CenterEnd
                    }
                    val icon = when (direction) {
                        DismissDirection.StartToEnd -> Icons.Default.Done
                        DismissDirection.EndToStart -> Icons.Default.Delete
                    }
                    val scale by animateFloatAsState(
                        if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f
                    )

                    Box(
                        Modifier
                            .fillMaxSize()
                            .background(color)
                            .padding(horizontal = 20.dp),
                        contentAlignment = alignment
                    ) {
                        Icon(
                            icon,
                            contentDescription = "Localized description",
                            modifier = Modifier.scale(scale)
                        )
                    }
                },
                dismissContent = {
                    Card(
                        elevation = animateDpAsState(
                            if (dismissState.dismissDirection != null) 4.dp else 0.dp
                        ).value
                    ) {
                        ItemRow(
                            product = item,
                            number = item.itemId
                        )
                    }
                }
            )
        }
    }
}

作为引用,另请参阅有关如何使用 Modifier.animateItemPlacement() 的其他方法,您可以查看 this example从谷歌发布。

关于android - SwipeToDismiss 在带有动画的 LazyColumn 内,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70066048/

相关文章:

kotlin - 错误: Kotlin :[内部错误] java.rmi.UnmarshalException

Android Facebook登录失败无异常,只是 "Sorry something went wrong"

android - 在不使用 fragment 的情况下构建首选项屏幕的最佳 Compose 方法是什么?

android - AnimatedVisibility 不会在 Jetpack Compose 的对话框中扩展高度

android - 如何从 res/values/arrays.xml 创建一个集合?

android - 可以持续更新通知区域的文字吗?

Android - 获取自签名服务器证书并添加到受信任的 keystore

android - 重新启动后重复警报管理器

android - 在 Kotlin 中,如何使属性只能由特定类型访问

android - 在 Jetpack Compose 中管理状态