鸿蒙提交

This commit is contained in:
Developer
2026-06-24 08:52:21 +08:00
parent 93d580fa1e
commit f9401b99e7
6 changed files with 623 additions and 53 deletions

View File

@@ -3,7 +3,7 @@
/// 创建时间: 2026-06-12
/// 更新时间: 2026-06-12
/// 作用: CustomPainter自绘天气图标支持动态主题色和动画效果
/// 上次更新: 小尺寸优化 — size<16 自动回退 CupertinoIcons 避免自绘细节丢失
/// 上次更新: 修复 SingleTickerProviderStateMixin 多 ticker 崩溃 → 改用 TickerProviderStateMixin
/// ============================================================
import 'dart:math' as math;
@@ -48,22 +48,29 @@ class WeatherIcon extends StatefulWidget {
/// 获取天气对应的图标颜色
static Color moodColor(WeatherPoetryMood mood, {bool isDark = false}) =>
switch (mood) {
WeatherPoetryMood.sunny => isDark ? const Color(0xFFFFD60A) : const Color(0xFFFFCC00),
WeatherPoetryMood.cloudy => isDark ? const Color(0xFF98989D) : const Color(0xFF8E8E93),
WeatherPoetryMood.rainy => isDark ? const Color(0xFF64D2FF) : const Color(0xFF5AC8FA),
WeatherPoetryMood.thunder => isDark ? const Color(0xFFBF5AF2) : const Color(0xFFAF52DE),
WeatherPoetryMood.snowy => isDark ? const Color(0xFFD1E9FF) : const Color(0xFFC7DFF0),
WeatherPoetryMood.foggy => isDark ? const Color(0xFFC7C7CC) : const Color(0xFFAEAEB2),
WeatherPoetryMood.windy => isDark ? const Color(0xFF64D2FF) : const Color(0xFF5AC8FA),
};
switch (mood) {
WeatherPoetryMood.sunny =>
isDark ? const Color(0xFFFFD60A) : const Color(0xFFFFCC00),
WeatherPoetryMood.cloudy =>
isDark ? const Color(0xFF98989D) : const Color(0xFF8E8E93),
WeatherPoetryMood.rainy =>
isDark ? const Color(0xFF64D2FF) : const Color(0xFF5AC8FA),
WeatherPoetryMood.thunder =>
isDark ? const Color(0xFFBF5AF2) : const Color(0xFFAF52DE),
WeatherPoetryMood.snowy =>
isDark ? const Color(0xFFD1E9FF) : const Color(0xFFC7DFF0),
WeatherPoetryMood.foggy =>
isDark ? const Color(0xFFC7C7CC) : const Color(0xFFAEAEB2),
WeatherPoetryMood.windy =>
isDark ? const Color(0xFF64D2FF) : const Color(0xFF5AC8FA),
};
@override
State<WeatherIcon> createState() => _WeatherIconState();
}
class _WeatherIconState extends State<WeatherIcon>
with SingleTickerProviderStateMixin {
with TickerProviderStateMixin {
AnimationController? _controller;
@override
@@ -109,7 +116,8 @@ class _WeatherIconState extends State<WeatherIcon>
@override
Widget build(BuildContext context) {
final isDark = AppTheme.isDarkMode(context);
final color = widget.color ?? WeatherIcon.moodColor(widget.mood, isDark: isDark);
final color =
widget.color ?? WeatherIcon.moodColor(widget.mood, isDark: isDark);
// 小尺寸优化16px以下回退到CupertinoIcons避免自绘细节丢失
if (widget.size < 16) {
@@ -127,14 +135,14 @@ class _WeatherIconState extends State<WeatherIcon>
/// 小尺寸回退图标 — 将天气类型映射到 CupertinoIcons
static IconData _fallbackIcon(WeatherPoetryMood mood) => switch (mood) {
WeatherPoetryMood.sunny => CupertinoIcons.sun_max,
WeatherPoetryMood.cloudy => CupertinoIcons.cloud_fill,
WeatherPoetryMood.rainy => CupertinoIcons.cloud_fill,
WeatherPoetryMood.thunder => CupertinoIcons.bolt_fill,
WeatherPoetryMood.snowy => CupertinoIcons.star_fill,
WeatherPoetryMood.foggy => CupertinoIcons.cloud_fill,
WeatherPoetryMood.windy => CupertinoIcons.wind,
};
WeatherPoetryMood.sunny => CupertinoIcons.sun_max,
WeatherPoetryMood.cloudy => CupertinoIcons.cloud_fill,
WeatherPoetryMood.rainy => CupertinoIcons.cloud_fill,
WeatherPoetryMood.thunder => CupertinoIcons.bolt_fill,
WeatherPoetryMood.snowy => CupertinoIcons.star_fill,
WeatherPoetryMood.foggy => CupertinoIcons.cloud_fill,
WeatherPoetryMood.windy => CupertinoIcons.wind,
};
Widget _buildPainter(Color color, double progress) => SizedBox(
width: widget.size,
@@ -143,15 +151,15 @@ class _WeatherIconState extends State<WeatherIcon>
);
CustomPainter _createPainter(WeatherPoetryMood mood, Color color, double p) =>
switch (mood) {
WeatherPoetryMood.sunny => SunnyPainter(color: color, progress: p),
WeatherPoetryMood.cloudy => CloudyPainter(color: color, progress: p),
WeatherPoetryMood.rainy => RainyPainter(color: color, progress: p),
WeatherPoetryMood.thunder => ThunderPainter(color: color, progress: p),
WeatherPoetryMood.snowy => SnowyPainter(color: color, progress: p),
WeatherPoetryMood.foggy => FoggyPainter(color: color, progress: p),
WeatherPoetryMood.windy => WindyPainter(color: color, progress: p),
};
switch (mood) {
WeatherPoetryMood.sunny => SunnyPainter(color: color, progress: p),
WeatherPoetryMood.cloudy => CloudyPainter(color: color, progress: p),
WeatherPoetryMood.rainy => RainyPainter(color: color, progress: p),
WeatherPoetryMood.thunder => ThunderPainter(color: color, progress: p),
WeatherPoetryMood.snowy => SnowyPainter(color: color, progress: p),
WeatherPoetryMood.foggy => FoggyPainter(color: color, progress: p),
WeatherPoetryMood.windy => WindyPainter(color: color, progress: p),
};
}
// ============================================================
@@ -177,10 +185,38 @@ Path _cloudPath(double left, double top, double w, double h) {
final p = Path();
final cx = left + w / 2, cy = top + h / 2;
p.moveTo(cx - w / 2, cy);
p.cubicTo(cx - w / 2, cy - h * 0.65, cx - w * 0.25, cy - h * 0.85, cx, cy - h * 0.5);
p.cubicTo(cx + w * 0.25, cy - h * 0.85, cx + w / 2, cy - h * 0.65, cx + w / 2, cy);
p.cubicTo(cx + w / 2, cy + h * 0.35, cx + w * 0.25, cy + h * 0.45, cx, cy + h * 0.35);
p.cubicTo(cx - w * 0.25, cy + h * 0.45, cx - w / 2, cy + h * 0.35, cx - w / 2, cy);
p.cubicTo(
cx - w / 2,
cy - h * 0.65,
cx - w * 0.25,
cy - h * 0.85,
cx,
cy - h * 0.5,
);
p.cubicTo(
cx + w * 0.25,
cy - h * 0.85,
cx + w / 2,
cy - h * 0.65,
cx + w / 2,
cy,
);
p.cubicTo(
cx + w / 2,
cy + h * 0.35,
cx + w * 0.25,
cy + h * 0.45,
cx,
cy + h * 0.35,
);
p.cubicTo(
cx - w * 0.25,
cy + h * 0.45,
cx - w / 2,
cy + h * 0.35,
cx - w / 2,
cy,
);
p.close();
return p;
}
@@ -222,15 +258,24 @@ class SunnyPainter extends CustomPainter {
final dx2 = outerR * math.cos(a), dy2 = outerR * math.sin(a);
final ray = Path()
..moveTo(dx1, dy1)
..cubicTo(dx1 + (dx2 - dx1) * 0.4, dy1 + (dy2 - dy1) * 0.2,
dx1 + (dx2 - dx1) * 0.6, dy1 + (dy2 - dy1) * 0.8, dx2, dy2);
..cubicTo(
dx1 + (dx2 - dx1) * 0.4,
dy1 + (dy2 - dy1) * 0.2,
dx1 + (dx2 - dx1) * 0.6,
dy1 + (dy2 - dy1) * 0.8,
dx2,
dy2,
);
canvas.drawPath(ray, rayStroke);
}
canvas.restore();
// 中心高光
canvas.drawCircle(const Offset(22, 22), 3,
_fillPaint(const Color(0xFFFFFFFF), alpha: 0.4));
canvas.drawCircle(
const Offset(22, 22),
3,
_fillPaint(const Color(0xFFFFFFFF), alpha: 0.4),
);
canvas.restore();
}
@@ -296,7 +341,10 @@ class RainyPainter extends CustomPainter {
final drop = Path()
..moveTo(x, dropY - 4)
..cubicTo(x - 0.5, dropY - 2, x - 0.3, dropY, x, dropY + 2);
canvas.drawPath(drop, _strokePaint(color, 1.8, alpha: alpha.clamp(0.0, 1.0)));
canvas.drawPath(
drop,
_strokePaint(color, 1.8, alpha: alpha.clamp(0.0, 1.0)),
);
}
canvas.restore();
@@ -329,17 +377,25 @@ class ThunderPainter extends CustomPainter {
final flashAlpha = _flashOpacity(progress);
// 闪电锯齿形状
final bolt = Path()
..moveTo(22, 20)..lineTo(17, 30)..lineTo(21, 30)
..lineTo(16, 42)..lineTo(27, 27)..lineTo(22, 27)
..lineTo(27, 20)..close();
..moveTo(22, 20)
..lineTo(17, 30)
..lineTo(21, 30)
..lineTo(16, 42)
..lineTo(27, 27)
..lineTo(22, 27)
..lineTo(27, 20)
..close();
canvas.drawPath(bolt, _fillPaint(color, alpha: flashAlpha));
// 闪电高光
if (flashAlpha > 0.5) {
canvas.drawPath(bolt, Paint()
..color = color.withValues(alpha: (flashAlpha * 0.3).clamp(0.0, 1.0))
..style = PaintingStyle.fill
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4));
canvas.drawPath(
bolt,
Paint()
..color = color.withValues(alpha: (flashAlpha * 0.3).clamp(0.0, 1.0))
..style = PaintingStyle.fill
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4),
);
}
canvas.restore();
@@ -400,11 +456,18 @@ class SnowyPainter extends CustomPainter {
final end = Offset(c.dx + r * math.cos(a), c.dy + r * math.sin(a));
canvas.drawLine(c, end, paint);
// 小分支
final mid = Offset(c.dx + r * 0.6 * math.cos(a), c.dy + r * 0.6 * math.sin(a));
final mid = Offset(
c.dx + r * 0.6 * math.cos(a),
c.dy + r * 0.6 * math.sin(a),
);
final br = r * 0.3;
for (final sign in [1, -1]) {
final ba = a + sign * math.pi / 6;
canvas.drawLine(mid, Offset(mid.dx + br * math.cos(ba), mid.dy + br * math.sin(ba)), paint);
canvas.drawLine(
mid,
Offset(mid.dx + br * math.cos(ba), mid.dy + br * math.sin(ba)),
paint,
);
}
}
}
@@ -434,7 +497,11 @@ class FoggyPainter extends CustomPainter {
..strokeCap = StrokeCap.round
..isAntiAlias = true;
const lines = [(16.0, 0.9, 14.0, 34.0), (24.0, 0.7, 10.0, 38.0), (32.0, 0.5, 16.0, 32.0)];
const lines = [
(16.0, 0.9, 14.0, 34.0),
(24.0, 0.7, 10.0, 38.0),
(32.0, 0.5, 16.0, 32.0),
];
for (var i = 0; i < 3; i++) {
final (y, alpha, startX, endX) = lines[i];
final dx = math.sin(progress * 2 * math.pi + i * 1.2) * 3.0;
@@ -442,8 +509,14 @@ class FoggyPainter extends CustomPainter {
stroke.strokeWidth = 2.5 - i * 0.5;
final path = Path()
..moveTo(startX + dx, y)
..cubicTo(startX + (endX - startX) * 0.25 + dx, y - 1.5,
startX + (endX - startX) * 0.75 + dx, y + 1.5, endX + dx, y);
..cubicTo(
startX + (endX - startX) * 0.25 + dx,
y - 1.5,
startX + (endX - startX) * 0.75 + dx,
y + 1.5,
endX + dx,
y,
);
canvas.drawPath(path, stroke);
}
@@ -475,7 +548,11 @@ class WindyPainter extends CustomPainter {
..strokeCap = StrokeCap.round
..isAntiAlias = true;
const windLines = [(14.0, 18.0, 2.0, 0.9), (10.0, 26.0, 2.2, 0.7), (16.0, 34.0, 1.8, 0.5)];
const windLines = [
(14.0, 18.0, 2.0, 0.9),
(10.0, 26.0, 2.2, 0.7),
(16.0, 34.0, 1.8, 0.5),
];
for (var i = 0; i < 3; i++) {
final (startX, y, sw, alpha) = windLines[i];
final lineLen = 20.0 + i * 4.0;