前言
上一篇 Jetpack Compose : 超简单实现侧滑删除 - 掘金 (juejin.cn) 很多人喜欢并且有同学想要威力加强版,今天它来了。
按照惯例效果图先行:

思路
威力加强版新增双侧滑动、多段展开和展开动画以便满足大家的日常需求。
虽然增加很多功能但思路不变还是通过Box堆叠并加上拖动手势实现,伪代码如下:
Box {
Box(modifier = Modifier.offset {
IntOffset(
x = state.offset.roundToInt(),
y = 0,
)
}
}
AnchoredDraggable
AnchoredDraggable 是一个基础 API,用于构建处于锚定状态的可拖动组件。
使用 DraggableAnchors 构建器方法定义锚点。然后,将它们传递给 AnchoredDraggableState的构造函数:
// 定义锚点左侧展开,左侧全展开,居中, 右侧展开,右侧全展开
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, //初始值,默认居中状态
anchors = anchors,
)
}
完整代码
fun SwipeBox(
actionWidth: Dp,
modifier: Modifier = Modifier,
control: SwipeBoxControl = rememberSwipeBoxControl(),
startAction: List<@Composable BoxScope.() -> Unit> = listOf(),
startFillAction: (@Composable BoxScope.() -> Unit)? = null,
endAction: List<@Composable BoxScope.() -> Unit> = listOf(),
endFillAction: (@Composable BoxScope.() -> Unit)? = null,
content: @Composable BoxScope.() -> Unit
) {
val scope = rememberCoroutineScope()
val density = LocalDensity.current
val actionWidthPx = with(density) {
actionWidth.toPx()
}
val startWidth = actionWidthPx * startAction.size
// startAction + startFillAction
val startActionSize = if (startFillAction == null) startAction.size else startAction.size + 1
val endWidth = actionWidthPx * endAction.size
// endAction + endFillAction
val endActionSize = if (endFillAction == null) endAction.size else endAction.size + 1
var contentWidth by remember { mutableFloatStateOf(0f) }
var contentHeight by remember { mutableFloatStateOf(0f) }
val state = remember(startWidth, endWidth, contentWidth) {
AnchoredDraggableState(
initialValue = DragAnchors.Center,
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
},
)
}
LaunchedEffect(control, state) {
with(control) {
handleControlEvents(
onStart = {
scope.launch {
state.animateTo(DragAnchors.Start)
}
},
onStartFill = {
scope.launch {
state.animateTo(DragAnchors.StartFill)
}
},
onCenter = {
scope.launch {
state.animateTo(DragAnchors.Center)
}
},
onEnd = {
scope.launch {
state.animateTo(DragAnchors.End)
}
},
onEndFill = {
scope.launch {
state.animateTo(DragAnchors.EndFill)
}
}
)
}
}
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()
}
}
}
@Stable
class SwipeBoxControl(
private val scope: CoroutineScope
) {
private sealed interface ControlEvent {
data object Start : ControlEvent
data object StartFill : ControlEvent
data object Center : ControlEvent
data object End : ControlEvent
data object EndFill : ControlEvent
}
private val controlEvents: MutableSharedFlow<ControlEvent> = MutableSharedFlow()
@OptIn(FlowPreview::class)
internal suspend fun handleControlEvents(
onStart: () -> Unit = {},
onStartFill: () -> Unit = {},
onCenter: () -> Unit = {},
onEnd: () -> Unit = {},
onEndFill: () -> Unit = {},
) = withContext(Dispatchers.Main) {
controlEvents.debounce(350).collect { event ->
when (event) {
ControlEvent.Start -> onStart()
ControlEvent.StartFill -> onStartFill()
ControlEvent.Center -> onCenter()
ControlEvent.End -> onEnd()
ControlEvent.EndFill -> onEndFill()
}
}
}
fun start() {
scope.launch { controlEvents.emit(ControlEvent.Start) }
}
fun startFill() {
scope.launch { controlEvents.emit(ControlEvent.Start) }
}
fun center() {
scope.launch { controlEvents.emit(ControlEvent.Center) }
}
fun end() {
scope.launch { controlEvents.emit(ControlEvent.End) }
}
fun endFill() {
scope.launch { controlEvents.emit(ControlEvent.EndFill) }
}
}
@Composable
fun rememberSwipeBoxControl(
coroutineScope: CoroutineScope = rememberCoroutineScope()
): SwipeBoxControl = remember(coroutineScope) { SwipeBoxControl(coroutineScope) }
enum class DragAnchors { Start, StartFill, Center, End, EndFill }
如何使用
@Composable
fun SwipeBoxScreen() {
val context = LocalContext.current
val control: SwipeBoxControl = rememberSwipeBoxControl()
SwipeBox(
control = control,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
actionWidth = 70.dp,
startAction = listOf {
Box(
modifier = Modifier
.background(colorResource(R.color.green))
.fillMaxSize()
.clickable {
control.center()
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 {
control.center()
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 {
control.center()
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 {
control.center()
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 {
control.center()
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
以上就是本篇文章的全部内容,如有问题欢迎指出,我们一起进步。
如果觉得本篇文章对您有帮助的话请点个赞让更多人看到吧,您的鼓励是我前进的动力。
谢谢~~