鸿蒙提交
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user