重置 SwipeToDismissBoxState 不起作用

问题描述 投票:0回答:1

我为惰性列构建了一个滑动解除框,其中包含一个确认对话框。 这是为了确保我们不会意外地从列表中删除项目,因此用户需要确认。

快乐的情况正在工作(用户滑动/用户确认),但是当我按下取消时,我正在重置状态(使用

SwipeToDismissBoxState.reset()
)。

除了我无法再次滑动该项目之外,一切似乎都已重置(甚至该项目正在动画返回)。 当项目移出视线(例如由于滚动)并重新进入屏幕时,它似乎根本没有重置。

我正在重置

SwipeToDismissBoxState.reset()
,我不知道我做错了什么......

Compose-多平台版本:1.6.11

这里是代码片段,一旦它起作用了,请随意重复使用它!


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun <T> SwipeToDismissContainer(
    item: T,
    itemName: String,
    animationTime: Int = 300,
    confirmationDialog: Boolean = true,
    onDismiss: (T) -> Unit,
    content: @Composable (T) -> Unit
) {
    var potentialDelete by remember { mutableStateOf(false) }
    var deleteItem by remember { mutableStateOf(false) }
    var resetItem by remember { mutableStateOf(false) }

    val state = rememberSwipeToDismissBoxState(
        confirmValueChange = { dismissValue ->
            when (dismissValue) {
                SwipeToDismissBoxValue.EndToStart,
                SwipeToDismissBoxValue.StartToEnd -> {
                    if (confirmationDialog) {
                        potentialDelete = true
                        true
                    } else {
                        potentialDelete = false
                        deleteItem = true
                        true
                    }
                }

                else -> false
            }
        }
    )

    LaunchedEffect(resetItem) {
        if (resetItem) {
            state.reset()
            resetItem = false
        }
    }

    LaunchedEffect(deleteItem) {
        if (deleteItem) {
            delay(animationTime.toLong())
            onDismiss(item)
        }
    }

    AnimatedVisibility(
        visible = !deleteItem,
        exit = shrinkVertically(
            animationSpec = tween(durationMillis = animationTime),
            shrinkTowards = Alignment.Top
        ) + fadeOut()
    ) {
        SwipeToDismissBox(
            state = state,
            backgroundContent = {
                Box(
                    contentAlignment = Alignment.CenterEnd,
                    modifier = Modifier
                        .fillMaxSize()
                        .background(MaterialTheme.colorScheme.errorContainer)
                ) {
                    Icon(
                        modifier = Modifier.minimumInteractiveComponentSize(),
                        imageVector = Icons.Outlined.Delete, contentDescription = null
                    )
                }
            },
            content = {
                content(item)
            }
        )
    }

    if (confirmationDialog && potentialDelete) {
        SwipeDismissConfirmationDialog(
            itemName = itemName,
            onCancel = {
                potentialDelete = false
                resetItem = true
            }
        ) {
            potentialDelete = false
            deleteItem = true
        }
    }
}

@Composable
private fun SwipeDismissConfirmationDialog(
    itemName: String,
    onCancel: () -> Unit,
    onConfirm: () -> Unit,
) {
    AlertDialog(
        icon = { Icon(Icons.Default.Delete, contentDescription = null) },
        title = { Text(text = stringResource(Res.string.item_deletion_dialog_title)) },
        text = {
            Text(
                text = stringResource(
                    Res.string.item_deletion_dialog_dialog_description,
                    itemName
                )
            )
        },
        onDismissRequest = {
            onCancel()
        },
        confirmButton = {
            TextButton(
                onClick = {
                    onConfirm()
                }
            ) {
                Text(text = stringResource(Res.string.item_deletion_dialog_dialog_confirm))
            }
        },
        dismissButton = {
            TextButton(
                onClick = {
                    onCancel()
                }
            ) {
                Text(text = stringResource(Res.string.item_deletion_dialog_dialog_cancel))
            }
        }
    )
}

kotlin-multiplatform compose-multiplatform compose-swipe-to-dismiss
1个回答
0
投票

据我所知,这要么是有意为之,要么是 M3 库中的错误。并不理想,但这是我正在使用的一个不错的解决方法。我还没有在 Multiplat 上测试过它,但它适用于 Android 原生。

基本思想是永远不确认值更改,然后保持滑动状态,就好像您已确认更改一样。我为那些可能想根据自己的需要使用代码的人添加了预览。

@Composable
fun <T> SwipeToDismissContainer(
    item: T,
    itemName: String,
    animationTime: Int = 300,
    confirmationDialog: Boolean = true,
    onDismiss: (T, onError: () -> Unit) -> Unit,
    content: @Composable (T) -> Unit
) {
    var potentialDelete by remember { mutableStateOf(false) }
    var deleteItem by remember { mutableStateOf(false) }
    var stateToMaintain by remember { mutableStateOf<SwipeToDismissBoxValue?>(null) }

    val state = rememberSwipeToDismissBoxState(
        confirmValueChange = { dismissValue ->
            when (dismissValue) {
                SwipeToDismissBoxValue.EndToStart,
                SwipeToDismissBoxValue.StartToEnd -> {
                    if (confirmationDialog) {
                        potentialDelete = true
                    } else {
                        potentialDelete = false
                        deleteItem = true
                    }
                    stateToMaintain = dismissValue
                }
                else -> {}
            }
            false //Immediately resets the state so we can swipe it again if confirmation is canceled or if deletion fails
        }
    )

    //Maintains the row's swiped state while it waits for confirmation and for AnimatedVisibility to hide the item
    LaunchedEffect(stateToMaintain) {
        stateToMaintain?.let {
            state.snapTo(it)
            stateToMaintain = null
        }
    }

    LaunchedEffect(deleteItem) {
        if (deleteItem) {
            delay(animationTime.toLong())
            onDismiss(item) {
                deleteItem = false
            }
        } else {
            //In our app, the onDismiss function also takes in an onError: () -> Unit,
            //which allows us to bring the item back if deletion fails
            state.reset()
        }
    }

    AnimatedVisibility(
        visible = !deleteItem,
        exit = shrinkVertically(
            animationSpec = tween(durationMillis = animationTime),
            shrinkTowards = Alignment.Top
        ) + fadeOut()
    ) {
        SwipeToDismissBox(
            state = state,
            backgroundContent = {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .background(MaterialTheme.colorScheme.errorContainer)
                )
            },
            content = {
                content(item)
            }
        )
    }

    val scope = rememberCoroutineScope()
    if (confirmationDialog && potentialDelete) {
        SwipeDismissConfirmationDialog(
            itemName = itemName,
            onCancel = {
                potentialDelete = false
                scope.launch { state.reset() } //reset() seems to only reset the visual state, not the full state object
            }
        ) {
            potentialDelete = false
            deleteItem = true
        }
    }
}

@Composable
private fun SwipeDismissConfirmationDialog(
    itemName: String,
    onCancel: () -> Unit,
    onConfirm: () -> Unit,
) {
    AlertDialog(
        title = { Text(text = "Delete Confirmation") },
        text = { Text("Delete $itemName?") },
        onDismissRequest = onCancel,
        confirmButton = {
            TextButton(onClick = onConfirm) {
                Text(text = "Delete")
            }
        },
        dismissButton = {
            TextButton(onClick = onCancel) {
                Text(text = "Cancel")
            }
        }
    )
}

@Preview(showBackground = true)
@Composable
private fun Test() {
    val list = (1..100).toList()
    val scope = rememberCoroutineScope()
    Column {
        list.forEach {
            SwipeToDismissContainer(
                it,
                it.toString(),
                onDismiss = { _, onError ->
                    scope.launch {
                        delay(1000)
                        onError()
                    }
                }
            ) { item ->
                Text(
                    text = item.toString(),
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(Color.White)
                        .padding(16.dp)
                )
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.