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

Compose 列表刷新自定义样式

效果如下

想过很多方案效果不太理想,最主要是手势触摸下拉上拉事件冲突,滑动偏移量不准。目前这种达到了效果,细节扩展还需自行实践

计算滑动的Touch事件捕捉监听

  • filter为 PointerEventType.Move触摸事件类型
  • PointerEventType.PressPointerEventType.MovePointerEventType.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))
                ) {}
            }

        }
    }
}
相关推荐
xiangpanf5 小时前
Laravel 10.x重磅升级:五大核心特性解析
android
robotx8 小时前
安卓线程相关
android
消失的旧时光-19438 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon9 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon9 小时前
VSYNC 信号完整流程2
android
dalancon9 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户693717500138410 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android11 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才12 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶12 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle