Jetpack Compose : 超简单实现侧滑删除

前言

侧滑删除可以说是开发中常见的功能之一,话不多说直接看效果图。

思路

通过Box堆叠并加上拖动手势实现,伪代码如下:

Kotlin 复制代码
Box {
	Box()
	Box(
		modifier = Modifier
                .graphicsLayer {
                    translationX = anchoredDraggableState.offset
                }
	)
}

拖动手势实现

AnchoredDraggable是compose提供的锚定状态的可拖动组件,我们可以通过它来实现拖动。

Kotlin 复制代码
    val minBound = 0f //描点的位置
    val maxBound = 100f //描点的位置
	
	val anchoredDraggableState = remember(maxBound) {
        AnchoredDraggableState(
            initialValue = checked,
            animationSpec = TweenSpec(durationMillis = 1000),
            anchors = DraggableAnchors {
                false at minBound
                true at maxBound
            },
            positionalThreshold = { distance -> distance * 0.5f }, //触发位置的阈值,这里设置0.5即中间位置时切换到下一个状态
            velocityThreshold = { maxBound } //拖动速度必须超过该阈值才能设置到下一个状态
        )
    }

完整代码

Kotlin 复制代码
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SwipeBox(
    checked: Boolean,
    onCheckedChange: ((Boolean) -> Unit)?,
    modifier: Modifier = Modifier,
    bottomContent: @Composable BoxScope.() -> Unit,
    content: @Composable BoxScope.() -> Unit
) {
    var bottomWidth by remember { mutableFloatStateOf(0f) }
    var bottomHeight by remember { mutableStateOf(0) }
    val minBound = 0f
    val maxBound = -bottomWidth
    var forceAnimationCheck by remember { mutableStateOf(false) }
    val anchoredDraggableState = remember(maxBound) {
        AnchoredDraggableState(
            initialValue = checked,
            animationSpec = TweenSpec(durationMillis = 1000),
            anchors = DraggableAnchors {
                false at minBound
                true at maxBound
            },
            positionalThreshold = { distance -> distance * 0.5f },
            velocityThreshold = { maxBound }
        )
    }
    val currentOnCheckedChange by rememberUpdatedState(onCheckedChange)
    val currentChecked by rememberUpdatedState(checked)
    LaunchedEffect(anchoredDraggableState) {
        snapshotFlow { anchoredDraggableState.currentValue }
            .collectLatest { newValue ->
                if (currentChecked != newValue) {
                    currentOnCheckedChange?.invoke(newValue)
                    forceAnimationCheck = !forceAnimationCheck
                }
            }
    }
    LaunchedEffect(checked, forceAnimationCheck) {
        if (checked != anchoredDraggableState.currentValue) {
            anchoredDraggableState.animateTo(checked)
        }
    }

    Box(
        modifier = modifier
            .anchoredDraggable(
                state = anchoredDraggableState,
                orientation = Orientation.Horizontal,
                enabled = onCheckedChange != null
            )
            .clipToBounds()
    ) {
        Box(
            modifier = Modifier
                .align(Alignment.CenterEnd)
                .height(with(LocalDensity.current) {
                    bottomHeight.toDp()
                })
                .onSizeChanged {
                    bottomWidth = it.width.toFloat()
                }
        ) {
            bottomContent()
        }
        Box(
            modifier = Modifier
                .graphicsLayer {
                    translationX = anchoredDraggableState.offset
                }
                .onSizeChanged {
                    bottomHeight = it.height
                }
        ) {
            content()
        }
    }
}

如何使用

Kotlin 复制代码
var checked by remember { mutableStateOf(false) }
    SwipeBox(
        checked = checked,
        onCheckedChange = { checked = it },
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentHeight(),
        bottomContent = {
            Row {
                Box(
                    modifier = Modifier
                        .width(70.dp)
                        .fillMaxHeight()
                        .background(colorResource(R.color.blue))
                ) {
                    Text(
                        text = "标为未读",
                        modifier = Modifier.align(Alignment.Center),
                        style = TextStyle.Default.copy(
                            color = colorResource(R.color.white),
                            fontSize = 12.sp
                        )
                    )
                }
                Box(
                    modifier = Modifier
                        .width(70.dp)
                        .fillMaxHeight()
                        .background(colorResource(R.color.yellow))
                ) {
                    Text(
                        text = "不显示",
                        modifier = Modifier.align(Alignment.Center),
                        style = TextStyle.Default.copy(
                            color = colorResource(R.color.white),
                            fontSize = 12.sp
                        )
                    )
                }
                Box(
                    modifier = Modifier
                        .width(70.dp)
                        .fillMaxHeight()
                        .background(colorResource(R.color.red))
                ) {
                    Text(
                        text = "删除",
                        modifier = Modifier.align(Alignment.Center),
                        style = TextStyle.Default.copy(
                            color = colorResource(R.color.white),
                            fontSize = 12.sp
                        )
                    )
                }
            }
        }
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .background(colorResource(R.color.white))
                .padding(20.dp, 10.dp)
        ) {
            Text(
                text = "小美",
                color = colorResource(R.color.text_333),
                fontSize = 14.sp
            )
            Spacer(Modifier.size(5.dp))
            Text(
                text = "我的电脑坏了,你能过来看看嘛。",
                color = colorResource(R.color.text_666),
                fontSize = 12.sp
            )
        }
    }

Thanks

以上就是本篇文章的全部内容,如有问题欢迎指出,我们一起进步。

如果觉得本篇文章对您有帮助的话请点个赞让更多人看到吧,您的鼓励是我前进的动力。

谢谢~~

源代码地址

推荐阅读

相关推荐
陈随易5 小时前
在 VSCode 里,把项目一键部署到服务器
前端·后端·程序员
Highcharts.js5 小时前
教程:基于 React + Highcharts 构建一个单页应用程序、按需数据拉取与图表渲染
开发语言·前端·数据结构·react.js·前端框架·highcharts·页面应用
To_OC5 小时前
我被 useState 坑了两次之后,终于把它的脾气摸透了
前端·javascript·react.js
鱼樱前端5 小时前
我用 Claude Code 后,编码效率翻 3 倍。但更值钱的是别的。
前端·后端·ai编程
_lucas6 小时前
给知识库网站接入AI问答
前端·ai编程·全栈
前端糕手6 小时前
前端面试题大全:JavaScript + Vue3 + React + TypeScript + 工程化 + 性能优化
前端
xingxiliang7 小时前
深入浅出 Android CTS 视频测试:从环境搭建、用例设计到底层通信原理
android·音视频
我命由我123457 小时前
Android 开发问题:为 PDFView 设置一个带有黑色边框的背景 drawable,但边框没有生效
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我不叫武8 小时前
一个用 Rust 写的离线编码查询桌面工具
前端
Web4Browser9 小时前
指纹浏览器 API 自动化怎么接:启动 Profile、获取 CDP 端点并连接自动化框架
前端·网络·typescript·自动化