本文使用的SwipeToDismiss是Material3 1.1.1版本的,与Material有少许差异。
声明如下
@Composable
@ExperimentalMaterial3Api
fun SwipeToDismiss(
state: DismissState,
background: @Composable RowScope.() -> Unit,
dismissContent: @Composable RowScope.() -> Unit,
modifier: Modifier = Modifier,
directions: Set<DismissDirection> = setOf(EndToStart, StartToEnd),
)
- state是组件的状态
- background是滑动时而显示在后方的内容
- dismissContent是可以移除的内容
- modifier就不用说了
- directions是可以滑动的方向,分别是 从右到左 和 从左到右
如何使用,这里只展示了从右到左的滑动
效果
代码
- rememberDismissState->confirmValueChange 在这里定义滑动放手后执行的函数
- rememberDismissState->positionalThreshold 定义滑动到什么位置改变状态
- Modifier.animateItemPlacement 添加移除时的动画
- 注意items务必添加key,否则会造成显示错乱
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(top = 8.dp),
) {
items(data, key = { it.id }) {
SwipeToDelete(
Modifier.animateItemPlacement(),//添加移除时的动画
content = it,
onDelete = { onDelete(it.id) },
onClick = { onClick(it) })
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SwipeToDelete(modifier: Modifier = Modifier,content: BaseContent, onDelete: () -> Unit, onClick: () -> Unit) {
val dismissState = rememberDismissState(confirmValueChange = {
if (it == DismissValue.DismissedToStart) {//滑动后放手会执行
onDelete()
true
} else {
false
}
}, positionalThreshold = {//滑动到什么位置会改变状态
it / 2
})
SwipeToDismiss(state = dismissState,
modifier = modifier
.padding(4.dp)
.fillMaxWidth(),
directions = setOf(DismissDirection.EndToStart),//只使用EndToStart
background = {
SwipeToDeleteBackground(dismissState = dismissState)
}, dismissContent = {
Card(
elevation = CardDefaults.cardElevation(
animateDpAsState(
if (dismissState.dismissDirection != null) 4.dp else 0.dp, label = ""
).value
), modifier = Modifier.clickable(onClick = onClick)
) {
Row {
AsyncImage(
model = content.thumbnailCoverUrl,
contentDescription = null,
modifier = Modifier
.padding(8.dp)
.width(100.dp),
placeholder = painterResource(id = R.drawable.ic_launcher_background),
contentScale = ContentScale.FillWidth
)
Text(
text = content.title, modifier = Modifier.padding(8.dp)
)
}
}
})
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SwipeToDeleteBackground(
dismissState: DismissState,
) {
val color by animateColorAsState( //一些动画
when (dismissState.targetValue) {
DismissValue.Default -> MaterialTheme.colorScheme.surface
DismissValue.DismissedToEnd -> Color.Green
DismissValue.DismissedToStart -> Color.Red
}, label = ""
)
val scale by animateFloatAsState(
if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f, label = ""
)
Box(
Modifier
.fillMaxSize()
.background(color)
.padding(horizontal = 20.dp),
contentAlignment = Alignment.CenterEnd
) {
Icon(
Icons.Default.Delete,
contentDescription = "Localized description",
modifier = Modifier.scale(scale)
)
}
}
解决轻扫(小范围快速滑动)触发删除问题
改动之前
改动之后
代码部分,这里有个技巧就是我用一个空组件去做重组范围的缩小
添加一个Float变量记录DismissState的progress(从 currentValue 到 targetValue 的进度),
百看不如一试,去具体实践一下你就会更好了解它的作用。
改动如下:
var currentProgress = remember {
mutableFloatStateOf(0f)
}
val dismissState = rememberDismissState(confirmValueChange = {
Log.d(TAG, "confirmValueChange: $it,$currentProgress")
if (it == DismissValue.DismissedToStart) {
if (currentProgress >= 0.5f && currentProgress < 1) {
onDelete()
return@rememberDismissState true
}
}
false
}, positionalThreshold = {
it / 2
})
//如果在这里使用LaunchedEffect,会造成当前组件频繁重组
ForUpdateData {/*缩小重组范围,减少重组*/
currentProgress = dismissState.progress
}
SwipeToDismiss(state = dismissState,
modifier = modifier
.padding(4.dp)
.fillMaxWidth(),
directions = setOf(DismissDirection.EndToStart),
background = {
//省略
}, dismissContent = {
//省略
}
})
@Composable
private fun ForUpdateData(onUpdate: () -> Unit) {
onUpdate()
}
参考:https://stackoverflow.com/questions/72676541/compose-swipetodismiss-confirmstatechange-applies-only-threshold