增加天气动画

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.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
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.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Slider
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.material3.*
import androidx.compose.runtime.*
import androidx.compose.animation.core.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.res.painterResource
import com.example.smarthome.R
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.foundation.layout.fillMaxHeight
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.LocalView
import android.view.SoundEffectConstants
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.geometry.Offset
import androidx.compose.foundation.layout.offset
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
fun MainScaffold(
@ -580,12 +555,13 @@ fun NavRailItem(text: String, iconRes: Int, selected: Boolean, onClick: () -> Un
@Composable
fun TopBar() {
val context = androidx.compose.ui.platform.LocalContext.current
var weatherInfo by remember { mutableStateOf(WeatherService.getSimulatedWeather()) }
var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
try {
weatherInfo = WeatherService.getWeather()
weatherInfo = WeatherService.getWeather(context)
} catch (e: Exception) {
// 使用模拟数据
}
@ -604,28 +580,106 @@ fun TopBar() {
@Composable
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(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(Color(0x50121212))
.padding(horizontal = 16.dp, vertical = 8.dp)
.background(backgroundBrush) // 使用动态渐变背景
) {
// 天气特效层(在背景之上,内容之下)- 使用固定大小
WeatherEffectLayer(
weather = weatherInfo.weather,
modifier = Modifier.size(width = 300.dp, height = 60.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(20.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 温度和天气
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = if (isLoading) "--" else "${weatherInfo.temperature}°C",
fontSize = 18.sp,
fontSize = 22.sp,
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 = if (isLoading) "加载中" else weatherInfo.weather,
fontSize = 12.sp,
color = Color(0xFF9AA0A6)
fontSize = 14.sp,
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(
modifier = Modifier
.width(1.dp)
.height(30.dp)
.background(Color(0x33FFFFFF))
.height(36.dp)
.background(Color.White.copy(alpha = 0.3f))
)
// 湿度
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = if (isLoading) "--" else "${weatherInfo.humidity}%",
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color.White
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White,
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
)
Text(
text = "湿度",
fontSize = 11.sp,
color = Color(0xFF9AA0A6)
fontSize = 13.sp,
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(
modifier = Modifier
.width(1.dp)
.height(30.dp)
.background(Color(0x33FFFFFF))
.height(36.dp)
.background(Color.White.copy(alpha = 0.3f))
)
// 空气质量
Column(horizontalAlignment = Alignment.CenterHorizontally) {
val aqiColor = when {
weatherInfo.aqi <= 50 -> Color(0xFF00E676)
weatherInfo.aqi <= 50 -> Color(0xFF4CAF50)
weatherInfo.aqi <= 100 -> Color(0xFFFFEB3B)
weatherInfo.aqi <= 150 -> Color(0xFFFF9800)
else -> Color(0xFFFF5252)
}
Text(
text = if (isLoading) "--" else weatherInfo.airQuality,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = aqiColor
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
color = aqiColor,
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
)
Text(
text = if (isLoading) "AQI" else "AQI ${weatherInfo.aqi}",
fontSize = 11.sp,
color = Color(0xFF9AA0A6)
fontSize = 13.sp,
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 = weatherInfo.city,
fontSize = 12.sp,
color = Color(0xFFB0B0B0)
fontSize = 15.sp,
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
fun AddRoomDialog(