
本文将带你逐层剖析一个完整的 Flutter 天气卡片应用源码,涵盖主题系统、枚举驱动 UI、交互动画、响应式布局与工程化细节,助你掌握现代 Flutter 应用的最佳实践。
🌐 加入社区 欢迎加入 开源鸿蒙跨平台开发者社区,获取最新资源与技术支持: 👉 开源鸿蒙跨平台开发者社区
完整效果


这个天气卡片应用以简洁优雅的卡片形式展示多个城市的实时天气信息。用户可通过左右滑动或点击“下一个”按钮切换城市,界面会根据当前天气类型自动切换配色方案,并伴有流畅的缩放过渡动画。
核心特性包括:
WeatherCondition 枚举的动态 UI 主题AppTheme 全局主题系统AnimationController + Transform.scale)WeatherCondition这是本项目最精妙的设计之一。我们没有硬编码颜色或图标,而是将所有与天气相关的视觉属性封装在 enum WeatherCondition 中:
enum WeatherCondition {
sunny, cloudy, rainy, stormy, snowy, foggy;
String get icon => ...;
String get description => ...;
Color get backgroundColor => ...;
Color get gradientStart => ...;
Color get textColor => ...;
}
✅ 优势:
这是“数据驱动 UI”思想的典型体现——UI 是数据的函数。
AppTheme通过静态常量和 ThemeData 工厂方法,统一管理应用色彩体系:
class AppTheme {
static const Color primaryColor = Color(0xFF4A90E2);
// ...
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: primaryColor),
scaffoldBackgroundColor: backgroundColor,
// ...
);
}
}
✅ 优势:
Colors.blue[500])。整个 UI 由 _buildWeatherContent() 组织,包含 AppBar、主体卡片、指示器、提示语和 FAB。
背景使用 LinearGradient,颜色来源于当前天气的 gradientStart 和 gradientEnd:
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
condition.gradientStart.withValues(alpha: 0.6),
condition.gradientEnd.withValues(alpha: 0.9),
Colors.white,
],
),
)注意:这里故意加入
Colors.white作为第三色,使底部更柔和,避免纯色块突兀。
_buildWeatherCard卡片采用圆角 + 双层阴影营造“浮起”效果:
BoxShadow(
color: condition.textColor.withValues(alpha: 0.15),
blurRadius: cardElevation,
offset: Offset(0, 10),
),
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 15,
offset: Offset(0, 5),
),
✅ 设计细节:
textColor,保证与天气风格一致。blurRadius,避免性能问题。Text('H: ${city.temperature + Random().nextInt(5)}°'),
Text('L: ${city.temperature - Random().nextInt(5)}°'),虽然真实项目中应使用 API 数据,但此处用
Random()模拟了“今日最高/最低温”的视觉效果,非常巧妙!
final color = quality == '优' ? Colors.green
: quality == '良' ? Colors.orange
: Colors.red;
配合半透明背景和边框,形成轻量级状态提示,符合 Material Design 的“微交互”理念。
使用 AnimationController 控制卡片缩放:
_scaleAnimation = Tween<double>(begin: 0.95, end: 1.0)
.animate(CurvedAnimation(parent: _cardController, curve: Curves.easeOutBack));在 AnimatedBuilder 中应用:
Transform.scale(scale: _scaleAnimation.value, child: _buildWeatherCard(...))✅ 动画流程:
_cardController.forward()(缩小到 0.95)setState 切换城市数据_cardController.reverse()(恢复到 1.0)使用
Curves.easeOutBack让回弹更有弹性,提升愉悦感。
通过 GestureDetector 监听水平拖拽结束事件:
onHorizontalDragEnd: (details) {
if (details.primaryVelocity! > 0) _prevCity(); // 右滑
else if (details.primaryVelocity! < 0) _nextCity(); // 左滑
}注意判空
details.primaryVelocity == null,避免异常。
项目通过 LayoutBuilder 获取屏幕约束,动态调整 UI:
final isSmallScreen = constraints.maxHeight < 600;
final isMediumScreen = constraints.maxHeight < 700;然后在各组件中使用条件判断:
SizedBox(height: isSmallScreen ? 20 : 32),
fontSize: isSmallScreen ? 16 : 18,
padding: EdgeInsets.all(isSmallScreen ? 20 : 32),✅ 覆盖场景:
通过 _isAnimating 标志防止用户快速多次点击:
Future<void> _navigateToCity(int newIndex) async {
if (_isAnimating || newIndex == _currentIndex) return;
_isAnimating = true;
// ... 动画逻辑
_isAnimating = false;
}重写 dispose() 方法,确保动画控制器被正确释放:
@override
void dispose() {
_cardController.dispose();
_indicatorController.dispose();
super.dispose();
}在 _buildWeatherExtras 中显示当前时间:
'更新时间: ${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')}'实际项目中应结合
Timer或状态管理库实现自动刷新。
虽然当前代码已非常完善,但在生产环境中还可进一步增强:
方向 | 建议 |
|---|---|
数据来源 | 接入真实天气 API(如 OpenWeatherMap) |
状态管理 | 引入 Riverpod / Bloc 管理城市列表与加载状态 |
国际化 | 使用 flutter_localizations 支持多语言 |
深色模式 | 扩展 AppTheme.darkTheme |
性能 | 对 Random().nextInt(5) 缓存结果,避免 rebuild 时波动 |
测试 | 编写 widget test 验证卡片渲染逻辑 |
这个天气卡片应用虽小,却集成了 Flutter 开发中的诸多最佳实践: