compose Android 模仿红包雨

首先找模型生成一段红包雨代码,如下

@Composable fun RedPacketRain() { val redPackets = remember { mutableStateListOfRed() } val coroutineScope = rememberCoroutineScope()

scss 复制代码
LaunchedEffect(Unit) {
    coroutineScope.launch {
        while (true) {
            delay(1000)
            redPackets.add(RedPacket())
        }
    }
}

Box(modifier = Modifier.fillMaxSize()) {
    redPackets.forEachIndexed { index, redPacket ->
        RedPacketItem(redPacket = redPacket, onRedPacketClicked = {
            redPackets.removeAt(index)
            // 处理红包点击事件
        })
    }
}

}

@Composable fun RedPacketItem(redPacket: RedPacket, onRedPacketClicked: () -> Unit) { val position by animateDpAsState( targetValue = redPacket.position, animationSpec = tween(durationMillis = 1000) )

ini 复制代码
Image(
    painter = painterResource(id = R.drawable.red_packet),
    contentDescription = "Red Packet",
    modifier = Modifier
        .offset(y = position.dp)
        .clickable { onRedPacketClicked() }
)

}

data class RedPacket(val position: Float = 0f)

遇到问题单个且动画有问题,然后进行改造

  1. 代码中利用Y轴进行0~1偏移,打印一直是0或者1,关键元素animateDpAsState用法通常以if true 1 else 0来达到插值器ValueAnimator过度动画效果

  2. 定义一个boolean控制Dp动画

    var boolean by remember { mutableStateOf(false) }

  3. 控制boolean变化,经过各种尝试最终选择在协程中调用

    LaunchedEffect(Unit) { boolean = true }

  4. 设置动画效果为线性

    animationSpec = tween(durationMillis = 5000, easing = LinearEasing)

  5. Image的Y轴偏移从屏幕起始到结束(屏幕高度*(0.0f~1.0f))

    .offset( y = LocalConfiguration.current.screenHeightDp.times(position.value).dp, )

  6. 要想红包在X轴随机出现还需要补充X轴偏移,定义一个随机区间值,利用屏幕宽度

    val width = LocalConfiguration.current.screenWidthDp.toFloat()

    fun randomRegion(min: Float, max: Float): Float {return (Math.random() * (max - min + 1) + min).toFloat()}

最终代码呈现

kotlin 复制代码
 @Composable
    fun RedPacketRain() {
        var isStart by remember {
            mutableStateOf(false)
        }
        var totalSum by remember { mutableStateOf(0) }
        Column(
            Modifier
                .fillMaxSize()
        ) {


            Row(
                Modifier
                    .fillMaxWidth()
                    .defaultMinSize(minHeight = 40.dp)
                    .padding(horizontal = 12.dp)
            ) {
                if (isStart.not()) {
                    Button(onClick = {
                        isStart = !isStart
                    }) {
                        Text(text = "开始")
                    }
                }
                Text(
                    text = "上次点中数量$totalSum",
                    Modifier
                        .padding(start = 12.dp)
                        .align(Alignment.CenterVertically)
                )
            }

            if (isStart) {
                val redPackets = remember { mutableStateListOf<RedPacket>() }
                val coroutineScope = rememberCoroutineScope()
                val context: Context = LocalContext.current
                val width = LocalConfiguration.current.screenWidthDp.toFloat()

                var total by remember { mutableStateOf(0) }
                LaunchedEffect(Unit) {
                    coroutineScope.launch {
                        while (total < 100) {
                            delay(500)
                            var xx = randomRegion(width / 6, width * 5 / 6)
                            Log.d("TAGG", "${width},position=$xx")
                            redPackets.add(
                                RedPacket(
                                    x = xx
                                )
                            )
                            total++
                            if (total >= 100) {
                                delay(5000)
                                isStart = false
                                totalSum=0
                            }
                        }
                    }
                }
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .background(Color.Gray)
                ) {
                    redPackets.forEachIndexed { index, redPacket ->

                        RedPacketItem(redPacket = redPacket, onRedPacketClicked = {
                            redPacket.y = 1.0f
                            redPacket.isRemoved = true
//                    redPackets.removeAt(index)
                            // 处理红包点击事件
//                    Toast.makeText(context, "Red Pack", Toast.LENGTH_SHORT).show()
                            totalSum++
                        })
                    }
                }
            }

        }

    }
}

fun randomRegion(min: Float, max: Float): Float {
//    Math.random().times(width - 30.dp.value).plus(30.dp.value).toFloat()
    return (Math.random() * (max - min + 1) + min).toFloat()
}


@Composable
fun RedPacketItem(redPacket: RedPacket, onRedPacketClicked: () -> Unit) {
    if (!redPacket.isRemoved) {
        var boolean by remember {
            mutableStateOf(false)
        }
        LaunchedEffect(Unit) {
            boolean = true
        }
        val position by animateDpAsState(
            targetValue = if (boolean) 1.dp else redPacket.y.dp,
            animationSpec = tween(durationMillis = 5000, easing = LinearEasing)
        )
        Log.d("TAG", "position=" + position.value)
        if (position.value != 0f) {
            Image(
                painter = painterResource(id = R.drawable.baseline_book_48),
                contentDescription = "Red Packet",
                modifier = Modifier
                    .offset(
                        y = LocalConfiguration.current.screenHeightDp.times(position.value).dp,
                        x = redPacket.x.dp
                    )
                    .clickable { onRedPacketClicked() }
            )
        }
    }


}

data class RedPacket(var y: Float = 0f, var x: Float = 0f, var isRemoved: Boolean = false)

备注:存在问题,点击未选中,跟正常红包雨降落幅度还存在着差异,只在模拟器中测试未检测卡顿

相关推荐
alexhilton1 小时前
如何用Perfetto来对启动优化去伪存真
android·kotlin·android jetpack
赏金术士2 小时前
Kotlin 从入门到进阶 之函数模块(核心基础)(二)
android·开发语言·kotlin
鱼儿也有烦恼4 小时前
8 issues were found when checking AAR metadata:
android
HalvmånEver4 小时前
MySQL的索引
android·linux·数据库·学习·mysql
赏金术士8 小时前
Kotlin 从入门到进阶 之作用域函数 & 优雅写法(五)
android·开发语言·kotlin
Ehtan_Zheng10 小时前
Android Compose 动画实践:内容切换与页面转场
android
Crystal32810 小时前
【终极指南】前端方面解决 uni-app APP 端 SSE 流式请求被缓冲拦截、无法实时渲染的问题
android·前端·ai编程
陆业聪11 小时前
技术选型决策树:什么团队、什么项目该选什么框架 | 跨平台框架深度对决(4)
android·架构设计
JohnnyDeng9412 小时前
Kotlin 协程原理与 Android 中的最佳实践
android·kotlin·协程
Aleyn13 小时前
用 KSP 给 Navigation 3 加一层「跨模块路由」:nav3-helper 设计与使用
android·android jetpack·composer