Compose 列表刷新自定义动画效果

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))
                ) {}
            }

        }
    }
}
相关推荐
警醒与鞭策7 小时前
Codex桌面版与Claude桌面版使用设置
android·unity·ai·性能优化
传奇开心果9 小时前
【Compose Multiplatform跨端声明式UI实用技术】第19课:多平台部署与发布
android·kotlin
红尘伴蝶舞9 小时前
加密拦截器让抓包失效,我只能从 4KB 的 logcat 碎片里拼回 JSON
android·kotlin·ai编程
booboosui9 小时前
219 个还是 0 个:读不到微信界面时,我只加不删
android
Android打工仔10 小时前
Compose 中何时该用 padding,何时该用 offset
android·android jetpack
二流小码农10 小时前
HarmonyOS开发助手,0代码让元服务从开发到上架
android·ios·harmonyos
limit007510 小时前
三维 WebGIS 工具选型:加载、样式、分析、交付,一套界面能做完什么
android·前端·web3·安卓·webgl
骑着蜗牛撵大象32711 小时前
让 Codex 值夜班:把告警排查与巡检接成可回滚的自动化链路
android·java·运维·ai·自动化·codex·智能体
2401_8858850411 小时前
芬兰语音通知接口怎样对接?语音提醒接入流程
android·前端·python·php·语音识别
程序猿老A12 小时前
轻量应用服务器怎么用phpcms搭建网站?
android