增加天气动画

This commit is contained in:
zzh 2025-11-27 12:32:01 +08:00
parent 0b4fbe3be2
commit 2c5afcfd6c

View File

@ -3,71 +3,46 @@ package com.example.smarthome.ui
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.*
import androidx.compose.material3.Switch import androidx.compose.runtime.*
import androidx.compose.material3.Slider import androidx.compose.animation.core.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import com.example.smarthome.R
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.BoxWithConstraints import com.example.smarthome.R
import com.example.smarthome.data.WeatherInfo
import com.example.smarthome.data.WeatherService
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import android.view.SoundEffectConstants import android.view.SoundEffectConstants
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.geometry.Offset
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import com.example.smarthome.data.WeatherService
import com.example.smarthome.data.WeatherInfo
@Composable @Composable
fun MainScaffold( fun MainScaffold(
@ -580,12 +555,13 @@ fun NavRailItem(text: String, iconRes: Int, selected: Boolean, onClick: () -> Un
@Composable @Composable
fun TopBar() { fun TopBar() {
val context = androidx.compose.ui.platform.LocalContext.current
var weatherInfo by remember { mutableStateOf(WeatherService.getSimulatedWeather()) } var weatherInfo by remember { mutableStateOf(WeatherService.getSimulatedWeather()) }
var isLoading by remember { mutableStateOf(true) } var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
try { try {
weatherInfo = WeatherService.getWeather() weatherInfo = WeatherService.getWeather(context)
} catch (e: Exception) { } catch (e: Exception) {
// 使用模拟数据 // 使用模拟数据
} }
@ -604,28 +580,106 @@ fun TopBar() {
@Composable @Composable
fun WeatherCard(weatherInfo: WeatherInfo, isLoading: Boolean) { fun WeatherCard(weatherInfo: WeatherInfo, isLoading: Boolean) {
// 动画:创建无限循环的偏移量
val infiniteTransition = rememberInfiniteTransition(label = "weather")
val offsetAnim by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1500f, // 增加偏移量,让流动范围更大
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 15000, easing = LinearEasing), // 加快一点速度
repeatMode = RepeatMode.Reverse
),
label = "gradient"
)
// 根据天气状况决定背景颜色 - 增加颜色差异让流动更明显
val colors = if (isLoading) {
listOf(
Color(0xFF2C3E50).copy(alpha = 0.6f),
Color(0xFF3498DB).copy(alpha = 0.6f),
Color(0xFF2C3E50).copy(alpha = 0.6f)
)
} else {
when {
// 晴天:暗橙色流动
weatherInfo.weather.contains("") ->
listOf(
Color(0xFFB8450A).copy(alpha = 0.55f),
Color(0xFFC06000).copy(alpha = 0.55f),
Color(0xFFB8450A).copy(alpha = 0.55f)
)
// 雨天:深紫深蓝流动
weatherInfo.weather.contains("") ->
listOf(
Color(0xFF2C3E50).copy(alpha = 0.6f),
Color(0xFF4CA1AF).copy(alpha = 0.6f),
Color(0xFF2C3E50).copy(alpha = 0.6f)
)
// 阴天/多云:灰蓝流动
weatherInfo.weather.contains("") || weatherInfo.weather.contains("") ->
listOf(
Color(0xFF606c88).copy(alpha = 0.6f),
Color(0xFF3f4c6b).copy(alpha = 0.6f),
Color(0xFF606c88).copy(alpha = 0.6f)
)
// 雪天:冰蓝流动
weatherInfo.weather.contains("") ->
listOf(
Color(0xFF5a7fa4).copy(alpha = 0.6f),
Color(0xFF8ad0d4).copy(alpha = 0.6f),
Color(0xFF5a7fa4).copy(alpha = 0.6f)
)
// 默认:蓝青流动
else ->
listOf(
Color(0xFF1090B0).copy(alpha = 0.6f),
Color(0xFF000046).copy(alpha = 0.6f),
Color(0xFF1090B0).copy(alpha = 0.6f)
)
}
}
val backgroundBrush = Brush.linearGradient(
colors = colors,
start = Offset(offsetAnim, offsetAnim),
end = Offset(offsetAnim + 500f, offsetAnim + 500f)
)
Box( Box(
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.background(Color(0x50121212)) .background(backgroundBrush) // 使用动态渐变背景
.padding(horizontal = 16.dp, vertical = 8.dp)
) { ) {
// 天气特效层(在背景之上,内容之下)- 使用固定大小
WeatherEffectLayer(
weather = weatherInfo.weather,
modifier = Modifier.size(width = 300.dp, height = 60.dp)
)
Row( Row(
horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(20.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// 温度和天气 // 温度和天气
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text( Text(
text = if (isLoading) "--" else "${weatherInfo.temperature}°C", text = if (isLoading) "--" else "${weatherInfo.temperature}°C",
fontSize = 18.sp, fontSize = 22.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color.White color = Color.White,
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
) )
Text( Text(
text = if (isLoading) "加载中" else weatherInfo.weather, text = if (isLoading) "加载中" else weatherInfo.weather,
fontSize = 12.sp, fontSize = 14.sp,
color = Color(0xFF9AA0A6) fontWeight = FontWeight.Medium,
color = Color.White,
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
) )
} }
@ -633,22 +687,28 @@ fun WeatherCard(weatherInfo: WeatherInfo, isLoading: Boolean) {
Box( Box(
modifier = Modifier modifier = Modifier
.width(1.dp) .width(1.dp)
.height(30.dp) .height(36.dp)
.background(Color(0x33FFFFFF)) .background(Color.White.copy(alpha = 0.3f))
) )
// 湿度 // 湿度
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text( Text(
text = if (isLoading) "--" else "${weatherInfo.humidity}%", text = if (isLoading) "--" else "${weatherInfo.humidity}%",
fontSize = 14.sp, fontSize = 18.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.SemiBold,
color = Color.White color = Color.White,
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
) )
Text( Text(
text = "湿度", text = "湿度",
fontSize = 11.sp, fontSize = 13.sp,
color = Color(0xFF9AA0A6) color = Color.White.copy(alpha = 0.8f),
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
) )
} }
@ -656,36 +716,46 @@ fun WeatherCard(weatherInfo: WeatherInfo, isLoading: Boolean) {
Box( Box(
modifier = Modifier modifier = Modifier
.width(1.dp) .width(1.dp)
.height(30.dp) .height(36.dp)
.background(Color(0x33FFFFFF)) .background(Color.White.copy(alpha = 0.3f))
) )
// 空气质量 // 空气质量
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
val aqiColor = when { val aqiColor = when {
weatherInfo.aqi <= 50 -> Color(0xFF00E676) weatherInfo.aqi <= 50 -> Color(0xFF4CAF50)
weatherInfo.aqi <= 100 -> Color(0xFFFFEB3B) weatherInfo.aqi <= 100 -> Color(0xFFFFEB3B)
weatherInfo.aqi <= 150 -> Color(0xFFFF9800) weatherInfo.aqi <= 150 -> Color(0xFFFF9800)
else -> Color(0xFFFF5252) else -> Color(0xFFFF5252)
} }
Text( Text(
text = if (isLoading) "--" else weatherInfo.airQuality, text = if (isLoading) "--" else weatherInfo.airQuality,
fontSize = 14.sp, fontSize = 18.sp,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.SemiBold,
color = aqiColor color = aqiColor,
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
) )
Text( Text(
text = if (isLoading) "AQI" else "AQI ${weatherInfo.aqi}", text = if (isLoading) "AQI" else "AQI ${weatherInfo.aqi}",
fontSize = 11.sp, fontSize = 13.sp,
color = Color(0xFF9AA0A6) color = Color.White.copy(alpha = 0.8f),
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
) )
} }
// 城市 // 城市
Text( Text(
text = weatherInfo.city, text = weatherInfo.city,
fontSize = 12.sp, fontSize = 15.sp,
color = Color(0xFFB0B0B0) fontWeight = FontWeight.Medium,
color = Color.White,
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
) )
} }
} }
@ -1544,6 +1614,130 @@ fun LightCard(name: String, percent: Float, modifier: Modifier = Modifier) {
} }
} }
@Composable
fun WeatherEffectLayer(weather: String, modifier: Modifier = Modifier) {
val infiniteTransition = rememberInfiniteTransition(label = "weather_effect")
// 太阳旋转动画
val sunRotation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(20000, easing = LinearEasing)
),
label = "sun_rotation"
)
// 雨雪下落动画
val dropOffset by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(1500, easing = LinearEasing)
),
label = "drop_offset"
)
// 云朵飘动动画
val cloudOffset by infiniteTransition.animateFloat(
initialValue = -20f,
targetValue = 20f,
animationSpec = infiniteRepeatable(
animation = tween(5000, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
label = "cloud_offset"
)
androidx.compose.foundation.Canvas(modifier = modifier) {
val width = size.width
val height = size.height
when {
// 晴天:绘制旋转的太阳
weather.contains("") -> {
// 太阳核心
val sunCenterX = width - 25.dp.toPx()
val sunCenterY = 25.dp.toPx()
drawCircle(
color = Color(0xFFFFD700).copy(alpha = 0.6f),
radius = 18.dp.toPx(),
center = Offset(sunCenterX, sunCenterY)
)
// 太阳光芒 - 使用数学计算位置
val rayLength = 10.dp.toPx()
val rayStart = 20.dp.toPx()
for (i in 0 until 8) {
val angle = Math.toRadians((sunRotation + i * 45f).toDouble())
val startX = sunCenterX + kotlin.math.cos(angle).toFloat() * rayStart
val startY = sunCenterY + kotlin.math.sin(angle).toFloat() * rayStart
val endX = sunCenterX + kotlin.math.cos(angle).toFloat() * (rayStart + rayLength)
val endY = sunCenterY + kotlin.math.sin(angle).toFloat() * (rayStart + rayLength)
drawLine(
color = Color(0xFFFFD700).copy(alpha = 0.5f),
start = Offset(startX, startY),
end = Offset(endX, endY),
strokeWidth = 2.dp.toPx()
)
}
}
// 雨天:绘制下落的雨滴
weather.contains("") -> {
val dropCount = 8
val dropLength = 8.dp.toPx()
for (i in 0 until dropCount) {
val x = (i * width / dropCount) + (i * 17 % 30)
val startY = (i * 29 % height.toInt()).toFloat()
val currentY = (startY + dropOffset * height) % height
drawLine(
color = Color.White.copy(alpha = 0.3f),
start = Offset(x, currentY),
end = Offset(x - 2.dp.toPx(), currentY + dropLength),
strokeWidth = 1.5f.dp.toPx()
)
}
}
// 雪天:绘制飘落的雪花(圆点)
weather.contains("") -> {
val snowCount = 8
for (i in 0 until snowCount) {
val x = (i * width / snowCount) + (i * 23 % 40)
val startY = (i * 41 % height.toInt()).toFloat()
val currentY = (startY + dropOffset * height * 0.5f) % height
drawCircle(
color = Color.White.copy(alpha = 0.5f),
radius = (1.5f + i % 2).dp.toPx(),
center = Offset(x, currentY)
)
}
}
// 多云/阴天:绘制云朵
weather.contains("") || weather.contains("") -> {
// 绘制几个叠加的圆组成云 - 缩小尺寸
val cloudColor = Color.White.copy(alpha = 0.15f)
val baseX = width - 35.dp.toPx() + cloudOffset * 0.5f
val baseY = 25.dp.toPx()
drawCircle(color = cloudColor, radius = 15.dp.toPx(), center = Offset(baseX, baseY))
drawCircle(color = cloudColor, radius = 12.dp.toPx(), center = Offset(baseX - 18.dp.toPx(), baseY + 5.dp.toPx()))
drawCircle(color = cloudColor, radius = 14.dp.toPx(), center = Offset(baseX + 15.dp.toPx(), baseY + 3.dp.toPx()))
drawCircle(color = cloudColor, radius = 10.dp.toPx(), center = Offset(baseX - 8.dp.toPx(), baseY - 8.dp.toPx()))
}
}
}
}
@Composable @Composable
fun AddRoomDialog( fun AddRoomDialog(