天气效果Kotlin+Compose+协程+Flow+Channel实现,以后天气效果不是梦

一、前言
我们在开发天气App时候,常见的有效果是根据天气展示实时UI效果,下雪的时候要展示下雪的效果,雷电雨时候展示雷电雨效果,这样才能让天气展示的更加真实,贴近人民生活。
本文将会展示是如何通过 Kotlin + Compose + 协程 + Flow + Channel
实现天气UI 中的雷电雨效果,整个设计采用分层设计分离:
- 背景层
- 下雨层
- 闪电层。
二、背景层
背景层没有什么特别的,使用 Compose
的 Canvas
的 drawRect
方法通过颜色绘制出一块背景,它是垂直渐变效果(Brush.verticalGradient
)::
less
Canvas(Modifier.fillMaxSize()) {
drawRect(
brush = Brush.verticalGradient(
colors = listOf(
Color(0xFF1A2980),
Color(0xFF26D0CE)
)
)
)
}
三、降雨层
- 先设计雨滴的属性,雨滴显示的位置:
X坐标,Y坐标
,雨滴下降速度:speed
,雨滴长度:length
,雨滴透明度:alpha
,雨滴下降到最大位置maxY
后肉眼看不到,类似消失,雨滴降落时受到横向风力导致左右摇摆幅度windEffect
。
kotlin
data class RainDrop(
val x: Float,//X坐标
val y: Float,//Y坐标
val speed: Float, //雨滴下降速度
val length: Float, //雨滴长度
val alpha: Float, //雨滴透明度
val maxY: Float, //雨滴下降到最大位置
val windEffect: Float //横向风力导致左右摇摆幅
)
- 开始创建雨滴列表:
intensity
:代表初始雨滴密度,竖直越大,代表雨越大,通过flow
实现 每隔60fps
,实时更新雨滴列表: 3. 更新雨滴列表中的雨滴(updateRainDrops()方法
):包含更新雨滴下降速度,受到风力影响的左右变化幅度,雨滴下降到最大位置maxY
后肉眼看不到后,随机创建天空新掉下来的雨滴。 4. 创建新的雨滴:createRainDrop()
方法:随机生成雨滴相关属性,产生新的雨滴 相关代码如下:
scss
class RainSystem(private val intensity: Float) {
val drops = mutableStateListOf<RainDrop>()
init {
repeat((800 * intensity).toInt()) {
drops.add(createRainDrop())
}
}
fun startAnimation(scope: CoroutineScope) {
scope.launch {
flow {
while (true) {
emit(Unit)
delay(16) // 60fps
}
}.collect {
updateRainDrops()
}
}
}
private fun updateRainDrops() {
drops.replaceAll { drop ->
if (drop.y > drop.maxY) createRainDrop()
else drop.copy(
y = drop.y + drop.speed,
x = drop.x + drop.windEffect
)
}
}
private fun createRainDrop() = RainDrop(
x = Random.nextFloat() * 2000 - 500,
y = -Random.nextFloat() * 200,
speed = 12f + Random.nextFloat() * 20 * intensity,
length = 10f + Random.nextFloat() * 25,
alpha = 0.3f + Random.nextFloat() * 0.7f,
maxY = 1500f + Random.nextFloat() * 500,
windEffect = Random.nextFloat() * 3 - 1.5f
)
}
- 通过Compose Canvas方法绘制雨滴:
ini
@Composable
fun RainLayer(
drops: List<RainDrop>,
) {
Canvas(Modifier.fillMaxSize()) {
drops.forEach { drop ->
drawLine(
brush = Brush.verticalGradient(
colors = listOf(
Color.White.copy(alpha = drop.alpha),
Color.White.copy(alpha = 0f)
)
),
start = Offset(drop.x, drop.y),
end = Offset(drop.x, drop.y + drop.length),
strokeWidth = 1.5f.dp.toPx()
)
}
}
}
四、闪电层
- 闪电层数据:闪电可以看成是一条一条不规则的线,绘制出来的。所以闪电最小单元数据模型只有线条的起始位置,换成坐标就4个,开始坐标
x1,y1
结束坐标x2,y2
kotlin
data class LightningSegment(
val x1: Float, val y1: Float,
val x2: Float, val y2: Float
)
- 开始创建闪电数据线条集合,
thunderFrequency
代表闪电密度,数值越大,闪电频率越高,isActive
的作用就是随机激活展示闪电标记,只有它为true时才生效,flashAlpha
闪电光透明度设置参数,然后通过flow
实现 每隔随机时间
,实时更新闪电绘制的路径: 3. 随机产生闪电绘制线条路径:generateLightningPath()
4. 闪电结束,重置闪电光透明度,所有相关代码如下:
kotlin
class LightningSystem(private val frequency: Float) {
var isActive by mutableStateOf(false)
var segments by mutableStateOf(emptyList<LightningSegment>())
var flashAlpha by mutableStateOf(0f)
fun startRandomStrikes(scope: CoroutineScope) {
scope.launch {
flow {
while (true) {
delay((1000..3000).random().toLong())
if (Random.nextFloat() < frequency) {
emit(Unit)
}
}
}.collect {
triggerLightning()
}
}
}
private suspend fun triggerLightning() {
isActive = true
generateLightningPath()
animateFlash()
delay(300)
isActive = false
}
private fun generateLightningPath() {
segments = buildList {
val startX = 300f + Random.nextFloat() * 600
var currentY = 0f
repeat(8) {
add(
LightningSegment(
startX + it * 50f, currentY,
startX + (it + 1) * 50f + Random.nextFloat() * 100 - 50,
currentY + 100f + Random.nextFloat() * 50
)
)
currentY += 100f + Random.nextFloat() * 50
}
}
}
private suspend fun animateFlash() {
flashAlpha = 0.9f
repeat(5) {
flashAlpha -= 0.15f
delay(30)
}
flashAlpha = 0f
}
}
- 绘制闪电路径:通过
Compose 的 Canvas
的drawRect
绘制闪电光,drawLine
方法绘制闪电路径
scss
@Composable
fun ThunderLayer(
isActive: Boolean,
segments: List<LightningSegment>,
flashAlpha: Float
) {
if (!isActive) return
Canvas(Modifier.fillMaxSize()) {
// 闪电闪光
drawRect(
color = Color.White.copy(alpha = flashAlpha * 0.6f)
)
// 闪电路径
segments.forEach { seg ->
drawLine(
color = Color(0xFFE3F2FD),
start = Offset(seg.x1, seg.y1),
end = Offset(seg.x2, seg.y2),
strokeWidth = 4f.dp.toPx(),
cap = StrokeCap.Round
)
}
}
}
五、整体雷电雨天气效果Compose
- 通过WeatherConfig来配置雨滴密度,闪电频率等
- 通过LaunchedEffect开启雨滴更新动画信息,闪电变化信息
scss
@Composable
fun WeatherScene() {
val scope = rememberCoroutineScope()
val config = remember { WeatherConfig() }
// 天气系统初始化
val rainSystem = remember { RainSystem(config.rainIntensity) }
val lightningSystem = remember { LightningSystem(config.thunderFrequency) }
// 启动天气动画
LaunchedEffect(Unit) {
rainSystem.startAnimation(scope)
lightningSystem.startRandomStrikes(scope)
}
BoxWithConstraints(Modifier.fillMaxSize()) {
// 背景层
SkyBackground()
// 降雨层
RainLayer(
drops = rainSystem.drops
)
// 闪电层
ThunderLayer(
isActive = lightningSystem.isActive, segments = lightningSystem.segments, flashAlpha = lightningSystem.flashAlpha
)
}
}
六、总结
本文简单介绍了雷电雨效果:Kotlin+Compose+协程+Flow 实现的步骤:
主要通过三层介绍:
- 背景层设计:绘制渐变背景
- 降雨层设计:雨滴产生,变化,控制雨滴密度决定大雨还是小雨
- 闪电层设计:闪电管,闪电路径