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)

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

相关推荐
恋猫de小郭1 天前
Flutter 正在计划提供 Packaged AI Assets 的支持,让你的包/插件可以更好被 AI 理解和选择
android·前端·flutter
AD钙奶-lalala1 天前
Android编译C++代码步骤详解
android·开发语言·c++
特立独行的猫a1 天前
Kuikly多端框架(KMP)实战:KMP中的 Ktor 网络库的多端适配指南
android·网络·harmonyos·ktor·compose·kmp·kuikly
滑板上的老砒霜1 天前
AI 共舞,还是被“注意力刺客”偷袭?——程序员的数字专注力守护指南
android·ai编程·客户端
钛态1 天前
Flutter for OpenHarmony 实战:Stack Trace — 异步堆栈调试专家
android·flutter·ui·华为·架构·harmonyos
二流小码农1 天前
2026年,在鸿蒙生态里,继续深耕自己
android·ios·harmonyos
2501_915106321 天前
iPhone 文件管理,如何进行应用沙盒文件查看
android·ios·小程序·https·uni-app·iphone·webview
非凡ghost1 天前
Ookla Speedtest安卓版(网速测试工具)
android·windows·学习·智能手机·软件需求
Flywith241 天前
【2025 年终总结】北漂五年,而立,婚礼,折叠车
android·前端·程序员
独自破碎E1 天前
题解 | 灵异背包?
android·java·开发语言