compose Android 模仿红包雨

compose Android 模仿红包雨

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

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

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

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

最终代码呈现

 @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)

compose Android 模仿红包雨

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