Compose 列表刷新自定义样式
效果如下
想过很多方案效果不太理想,最主要是手势触摸下拉上拉事件冲突,滑动偏移量不准。目前这种达到了效果,细节扩展还需自行实践
计算滑动的Touch事件捕捉监听
- filter为
PointerEventType.Move
触摸事件类型 PointerEventType.Press
、PointerEventType.Move
、PointerEventType.Release
对应按下、移动和释放offsetY
计算滑动距离,距离差值过大所以除几倍到正常范围isStart
控制动画
ini
.pointerInput(filter) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
// handle pointer event Press->Move->Release
println("${event.type}")
when (event.type) {
PointerEventType.Press -> {
initY = event.changes.first().position.y
isStart = true
}
PointerEventType.Move -> {
offsetY = event.changes.first().position.y - initY
}
PointerEventType.Release -> {
offsetY = 0f;
isStart = false
}
}
}
}
}
留出空白动画区域
- 用
drawBehind
绘制下拉弧度和椭圆
ini
.drawBehind {
val path = Path()
var h = offsetY.div(3)
path.moveTo(0f, 0f)
path.cubicTo(
0f,
0f,
size.width / 2,
h,
size.width,
0f
)
drawPath(path, primaryColor)
val s = Size(size.width.div(3), h)
drawOval(
Color.White,
topLeft = Offset(size.width.div(2) - s.width.div(2), 4.dp.value),
size = s
)
}
compose lottie库使用
- 布局里做一个高度变化的gif
ini
LottieAnimation(
composition = composition3,
progress = { lottieAnimatable.value },
modifier = Modifier
.fillMaxWidth()
.height(
offsetY
.div(24).dp
)
)
完整代码
scss
@Composable
private fun LogPointerEvents() {
val filter = PointerEventType.Move
val state = rememberLazyListState()
var offsetY by remember {
mutableFloatStateOf(0f)
}
var initY by remember {
mutableFloatStateOf(0f)
}
val primaryColor = MaterialTheme.colorScheme.primary
val composition3 by rememberLottieComposition(LottieCompositionSpec.Asset("Polite Chicky.json"))
val lottieAnimatable = rememberLottieAnimatable()
var isStart by remember {
mutableStateOf(false)
}
if (isStart) {
LaunchedEffect(Unit) {
lottieAnimatable.animate(
composition3,
iterations = LottieConstants.IterateForever,
clipSpec = LottieClipSpec.Progress(0.5f, 0.75f),
)
}
}
Column(
Modifier
.fillMaxSize()
.background(Color(0xFFfefefe))
.pointerInput(filter) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
// handle pointer event Press->Move->Release
println("${event.type}")
when (event.type) {
PointerEventType.Press -> {
initY = event.changes.first().position.y
isStart = true
}
PointerEventType.Move -> {
offsetY = event.changes.first().position.y - initY
}
PointerEventType.Release -> {
offsetY = 0f;
isStart = false
}
}
}
}
}
.drawBehind {
val path = Path()
var h = offsetY.div(3)
path.moveTo(0f, 0f)
path.cubicTo(
0f,
0f,
size.width / 2,
h,
size.width,
0f
)
drawPath(path, primaryColor)
val s = Size(size.width.div(3), h)
drawOval(
Color.White,
topLeft = Offset(size.width.div(2) - s.width.div(2), 4.dp.value),
size = s
)
}) {
if (state.isScrollInProgress && !state.canScrollBackward) {
LottieAnimation(
composition = composition3,
progress = { lottieAnimatable.value },
modifier = Modifier
.fillMaxWidth()
.height(
offsetY
.div(24).dp
)
)
}
LazyColumn(
state = state,
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(12.dp)
) {
items(30) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
colors = CardDefaults.cardColors(containerColor = Color(0xFFbdc9ce))
) {}
}
}
}
}