如果我想在滑动后调用操作,如何在 Compose 中实现滑动

Mag*_*cka 5 android kotlin android-jetpack-compose

我在撰写时遇到问题。我尝试实现简单的日历。我已经显示了月份,并启用了通过箭头更改月份。

在此输入图像描述

现在我想通过滑动添加更改月份。我尝试过.swipeable

这是示例:

val size = remember { mutableStateOf(Size.Zero) }
val swipeableState = rememberSwipeableState(0)
val width = if (size.value.width == 0f) 1f else size.value.width - 60.dp.value * 2
Box(
    modifier = Modifier
        .fillMaxWidth()
        .onSizeChanged { size.value = Size(it.width.toFloat(), it.height.toFloat()) }
        .swipeable(
            state = swipeableState,
            anchors = mapOf(0f to 0, width to 1),
            thresholds = { _, _ -> FractionalThreshold(0.3f) },
            orientation = Orientation.Horizontal
        )
        .background(Color.LightGray)
) {
    Box(
        Modifier
            .offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
            .size(60.dp)
            .background(Color.DarkGray)
    )
}
Run Code Online (Sandbox Code Playgroud)

但这并不完美。我需要保留在日历区域之外,并且我不知道如何更改月份状态(响应显示日历)。

我的问题是“如果我想在滑动后调用操作,如何实现滑动”。

Phi*_*hov 14

您可以通过Side Effects观察任何状态。如果您需要等待操作完成,可以使用if+ DisposableEffect

if (swipeableState.isAnimationRunning) {
    DisposableEffect(Unit) {
        onDispose {
            println("animation finished")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当动画开始时,我创建一个DisposableEffect,当它结束时,onDispose被调用,这意味着动画完成。

对于您的代码,这也会在启动时触发,因为当您在 之后更改锚点时onSizeChanged,这也会启动动画。但你可以检查该值是否已更改,因此问题不大。

为了解决你的基本问题,你还需要有三种状态:左、中、右。


关于这句话,有点题外话:

val width = if (size.value.width == 0f) 1f else size.value.width - 60.dp.value * 2
Run Code Online (Sandbox Code Playgroud)
  1. 每次重组时都重复此计算,您不应该这样做。您可以使用rememeberwithsize.value.width作为键,因为width只有当该值发生变化时才需要重新计算。
  2. 您试图通过乘以 2 来从 DP 获取像素值。这是错误的,您需要使用 Density。

所以最终的代码可以如下所示:

enum class SwipeDirection(val raw: Int) {
    Left(0),
    Initial(1),
    Right(2),
}

@Composable
fun TestScreen() {
    var size by remember { mutableStateOf(Size.Zero) }
    val swipeableState = rememberSwipeableState(SwipeDirection.Initial)
    val density = LocalDensity.current
    val boxSize = 60.dp
    val width = remember(size) {
        if (size.width == 0f) {
            1f
        } else {
            size.width - with(density) { boxSize.toPx() }
        }
    }
    val scope = rememberCoroutineScope()
    if (swipeableState.isAnimationRunning) {
        DisposableEffect(Unit) {
            onDispose {
                when (swipeableState.currentValue) {
                    SwipeDirection.Right -> {
                        println("swipe right")
                    }
                    SwipeDirection.Left -> {
                        println("swipe left")
                    }
                    else -> {
                        return@onDispose
                    }
                }
                scope.launch {
                    // in your real app if you don't have to display offset,
                    // snap without animation
                    // swipeableState.snapTo(SwipeDirection.Initial)
                    swipeableState.animateTo(SwipeDirection.Initial)
                }
            }
        }
    }
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .onSizeChanged { size = Size(it.width.toFloat(), it.height.toFloat()) }
            .clickable { }
            .swipeable(
                state = swipeableState,
                anchors = mapOf(
                    0f to SwipeDirection.Left,
                    width / 2 to SwipeDirection.Initial,
                    width to SwipeDirection.Right,
                ),
                thresholds = { _, _ -> FractionalThreshold(0.3f) },
                orientation = Orientation.Horizontal
            )
            .background(Color.LightGray)
    ) {
        Box(
            Modifier
                .offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
                .size(boxSize)
                .background(Color.DarkGray)
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

ps 如果您在滑动过程中不需要设置任何动画,我已将此逻辑移至单独的修改器中。