前言
上一篇 Jetpack Compose : 超简单实现侧滑删除 - 掘金 (juejin.cn) 很多人喜欢并且有同学想要威力加强版,今天它来了。
按照惯例效果图先行:
思路
威力加强版新增双侧滑动、多段展开和展开动画以便满足大家的日常需求。
虽然增加很多功能但思路不变还是通过Box堆叠并加上拖动手势实现,伪代码如下:
Kotlin
Box {
Box()
Box(modifier = Modifier
.offset {
IntOffset(
x = state.offset.roundToInt(),
y = 0,
)
}
)
}
AnchoredDraggable
AnchoredDraggable 是一个基础 API,用于构建处于锚定状态的可拖动组件。
使用 DraggableAnchors 构建器方法定义锚点。然后,将它们传递给 AnchoredDraggableState的构造函数:
Kotlin
// 定义锚点左侧展开,左侧全展开,居中, 右侧展开,右侧全展开
enum class DragAnchors { Start, StartFill, Center, End, EndFill }
val anchors = DraggableAnchors {
Start at 100.dp.toPx() //这边直接使用数值.dp,方便大家直观理解
StartFill at 200.dp.toPx()
Center at 0f
End at -100.dp.toPx()
EndFill at -200.dp.toPx()
}
val state = remember {
AnchoredDraggableState(
initialValue = DragAnchors.Center, //初始值,默认居中状态
animationSpec = TweenSpec(durationMillis = 350), //动画
anchors = anchors,
positionalThreshold = { distance -> distance * 0.5f }, //位置阈值 50% 时进行锚点切换
velocityThreshold = { with(density) { 100.dp.toPx() } }, //速度阈值,这个不是固定值,只是我喜欢写100
)
}
完整代码
Kotlin
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SwipeBox(
modifier: Modifier = Modifier,
actionWidth: Dp, //锚点宽度
startAction: List<@Composable BoxScope.() -> Unit>, //左侧展开锚点
startFillAction: (@Composable BoxScope.() -> Unit)? = null, //左侧全展开锚点
endAction: List<@Composable BoxScope.() -> Unit>, //右侧展开锚点
endFillAction: (@Composable BoxScope.() -> Unit)? = null, //右侧全展开锚点
content: @Composable BoxScope.() -> Unit
) {
val density = LocalDensity.current
val actionWidthPx = with(density) {
actionWidth.toPx()
}
val startWidth = actionWidthPx * startAction.size //左侧锚点宽度
val startActionSize = startAction.size + 1 //左侧锚点总数 = startAction + startFillAction
val endWidth = actionWidthPx * endAction.size //右侧锚点宽度
val endActionSize = endAction.size + 1 //右侧锚点总数 = endAction + endFillAction
var contentWidth by remember { mutableFloatStateOf(0f) } //内容组件宽度
var contentHeight by remember { mutableFloatStateOf(0f) }
val state = remember(startWidth, endWidth, contentWidth) {
AnchoredDraggableState(
initialValue = DragAnchors.Center,
animationSpec = TweenSpec(durationMillis = 350),
anchors = DraggableAnchors {
DragAnchors.Start at (if (startFillAction != null) actionWidthPx else 0f) + startWidth //左侧全展开锚点宽度 + 左侧展开锚点宽度
DragAnchors.StartFill at (if (startFillAction != null) contentWidth else 0f) + startWidth // 内容组件宽度 + 左侧展开锚点宽度
DragAnchors.Center at 0f
DragAnchors.End at (if (endFillAction != null) -actionWidthPx else 0f) - endWidth //右侧全展开锚点宽度 + 右侧展开锚点宽度
DragAnchors.EndFill at (if (endFillAction != null) -contentWidth else 0f) - endWidth // 内容组件宽度 + 右侧展开锚点宽度
},
positionalThreshold = { distance -> distance * 0.5f },
velocityThreshold = { with(density) { 100.dp.toPx() } },
)
}
Box(
modifier = modifier
.anchoredDraggable(
state = state,
orientation = Orientation.Horizontal,
)
.clipToBounds()
) {
startAction.forEachIndexed { index, action ->
Box(
modifier = Modifier
.align(Alignment.CenterStart)
.width(actionWidth)
.height(with(density) {
contentHeight.toDp()
})
.offset {
IntOffset(
x = if (state.offset <= actionWidthPx * startActionSize) {
(-actionWidthPx + state.offset / startActionSize * (startActionSize - index)).roundToInt()
} else {
(-actionWidthPx * (index + 1) + state.offset).roundToInt()
},
y = 0,
)
}
) {
action()
}
}
startFillAction?.let {
Box(
modifier = Modifier
.align(Alignment.CenterStart)
.height(with(density) {
contentHeight.toDp()
})
.offset {
IntOffset(
x = if (state.offset <= actionWidthPx * startActionSize) {
(-contentWidth + state.offset / startActionSize).roundToInt()
} else {
(-contentWidth - startWidth + state.offset).roundToInt()
},
y = 0,
)
}
) {
it()
}
}
endAction.forEachIndexed { index, action ->
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.width(actionWidth)
.height(with(density) {
contentHeight.toDp()
})
.offset {
IntOffset(
x = if (state.offset >= -(actionWidthPx * endActionSize)) {
(actionWidthPx + state.offset / endActionSize * (endActionSize - index)).roundToInt()
} else {
(actionWidthPx * (index + 1) + state.offset).roundToInt()
},
y = 0,
)
}
) {
action()
}
}
endFillAction?.let {
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.height(with(density) {
contentHeight.toDp()
})
.offset {
IntOffset(
x = if (state.offset >= -(actionWidthPx * endActionSize)) {
(contentWidth + state.offset / endActionSize).roundToInt()
} else {
(contentWidth + endWidth + state.offset).roundToInt()
},
y = 0,
)
}
) {
it()
}
}
Box(
modifier = Modifier
.onSizeChanged {
contentWidth = it.width.toFloat()
contentHeight = it.height.toFloat()
}
.offset {
IntOffset(
x = state.offset.roundToInt(),
y = 0,
)
}
) {
content()
}
}
}
enum class DragAnchors { Start, StartFill, Center, End, EndFill }
如何使用
Kotlin
@Composable
fun SwipeBoxScreen() {
val context = LocalContext.current
SwipeBox(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
actionWidth = 70.dp,
startAction = listOf {
Box(
modifier = Modifier
.background(colorResource(R.color.green))
.fillMaxSize()
.clickable {
Toast.makeText(context, "置顶", Toast.LENGTH_SHORT).show()
}
) {
Text(
text = "置顶",
modifier = Modifier.align(Alignment.Center),
style = TextStyle.Default.copy(
color = colorResource(R.color.white),
fontSize = 12.sp
)
)
}
},
startFillAction = {
Box(
modifier = Modifier
.background(colorResource(R.color.pink))
.fillMaxSize()
) {
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.width(70.dp)
.fillMaxHeight()
.clickable {
Toast.makeText(context, "取消置顶", Toast.LENGTH_SHORT).show()
}
) {
Text(
text = "取消置顶",
modifier = Modifier.align(Alignment.Center),
style = TextStyle.Default.copy(
color = colorResource(R.color.white),
fontSize = 12.sp
)
)
}
}
},
endAction = listOf(
{
Box(
modifier = Modifier
.background(colorResource(R.color.blue))
.fillMaxSize()
.clickable {
Toast.makeText(context, "标为未读", Toast.LENGTH_SHORT).show()
}
) {
Text(
text = "标为未读",
modifier = Modifier.align(Alignment.Center),
style = TextStyle.Default.copy(
color = colorResource(R.color.white),
fontSize = 12.sp
)
)
}
},
{
Box(
modifier = Modifier
.background(colorResource(R.color.yellow))
.fillMaxSize()
.clickable {
Toast.makeText(context, "不显示", Toast.LENGTH_SHORT).show()
}
) {
Text(
text = "不显示",
modifier = Modifier.align(Alignment.Center),
style = TextStyle.Default.copy(
color = colorResource(R.color.white),
fontSize = 12.sp
)
)
}
}
),
endFillAction = {
Box(
modifier = Modifier
.background(colorResource(R.color.red))
.fillMaxSize()
.clickable {
Toast.makeText(context, "删除", Toast.LENGTH_SHORT).show()
}
) {
Box(
modifier = Modifier
.align(Alignment.CenterStart)
.width(70.dp)
.fillMaxHeight()
) {
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
以上就是本篇文章的全部内容,如有问题欢迎指出,我们一起进步。
如果觉得本篇文章对您有帮助的话请点个赞让更多人看到吧,您的鼓励是我前进的动力。
谢谢~~
源代码地址
推荐阅读
- Jetpack Compose : 从改造你的登录页面开始 - 掘金 (juejin.cn)
- Jetpack Compose : 一学就会的自定义下拉刷新&加载更多 - 掘金 (juejin.cn)
- Jetpack Compose : 优雅的使用WebView - 掘金 (juejin.cn)
- Jetpack Compose : 一文学会嵌套滚动NestedScrollConnection - 掘金 (juejin.cn)
- Jetpack Compose : 超简单实现滚轮控件(WheelPicker) - 掘金 (juejin.cn)
- Jetpack Compose : 超简单实现文本展开和收起 - 掘金 (juejin.cn)