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)

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

相关推荐
豆 腐1 小时前
MySQL【四】
android·数据库·笔记·mysql
想取一个与众不同的名字好难3 小时前
android studio导入OpenCv并改造成.kts版本
android·ide·android studio
Jewel1054 小时前
Flutter代码混淆
android·flutter·ios
Yawesh_best5 小时前
MySQL(5)【数据类型 —— 字符串类型】
android·mysql·adb
曾经的三心草8 小时前
Mysql之约束与事件
android·数据库·mysql·事件·约束
guoruijun_2012_412 小时前
fastadmin多个表crud连表操作步骤
android·java·开发语言
Winston Wood12 小时前
一文了解Android中的AudioFlinger
android·音频
B.-13 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
有趣的杰克13 小时前
Flutter【04】高性能表单架构设计
android·flutter·dart
大耳猫19 小时前
主动测量View的宽高
android·ui