Flutter 实战:weather 天气应用的城市搜索、本地天气数据与鸿蒙适配解析
Flutter 实战:weather 天气应用的城市搜索、本地天气数据与鸿蒙适配解析
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
weather 是一个基于 Flutter 实现的本地天气演示应用。它内置了多个城市的天气数据,支持通过搜索框过滤城市,点击城市后切换当前天气卡片,并展示温度、天气描述、湿度和风速,以及对应的天气图标和颜色。
本文基于项目真实源码展开,重点分析 本地天气数据结构、城市搜索过滤、当前城市切换、天气条件配色、主卡片布局、辅助指标展示 和 鸿蒙适配关注点。文章内容可直接发布到 CSDN,不包含面向作者的检查说明。
这个项目最重要的边界是:它展示的是本地天气演示数据,而不是联网天气服务。把边界写清楚,技术文章才可靠。

图示说明:本文围绕 Flutter 天气应用的城市搜索、本地天气卡片、条件配色和跨端适配展开,适合用于鸿蒙、Android、iOS 等多端应用开发复盘。
一、项目定位与功能概览
1.1 应用主题
weather 的定位是一个 本地天气查询演示应用。用户可以在顶部搜索城市,列表会实时过滤;点击城市后,页面中央会展示该城市的温度、天气状况、湿度和风速。
核心功能如下:
| 功能 | 页面表现 | 源码实现 |
|---|---|---|
| 搜索城市 | 顶部搜索框 | _searchController、_searchCity() |
| 当前城市 | 主天气卡片 | _selectedCity、_currentWeather |
| 温度展示 | 大字号 22°C |
weather['temp'] |
| 天气状态 | Sunny、Cloudy 等 | weather['condition'] |
| 湿度展示 | Humidity 卡片 | weather['humidity'] |
| 风速展示 | Wind 卡片 | weather['wind'] |
| 其他城市 | 下方列表 | _cities.where(...) |
| 城市切换 | 点击列表项 | onTap |
1.2 默认数据
项目启动后已经内置城市天气:
| 城市 | 温度 | 天气 | 湿度 | 风速 |
|---|---|---|---|---|
| New York | 22°C | Sunny | 65 | 12 km/h |
| London | 15°C | Cloudy | 80 | 8 km/h |
| Tokyo | 28°C | Clear | 55 | 6 km/h |
| Sydney | 18°C | Rainy | 90 | 15 km/h |
| Paris | 20°C | Partly Cloudy | 70 | 10 km/h |
| Dubai | 38°C | Hot | 40 | 5 km/h |
1.3 学习价值
这个项目适合学习以下 Flutter 实战能力:
- 如何用
List<Map<String, dynamic>>保存城市天气数据。 - 如何通过搜索词过滤城市列表。
- 如何点击列表项切换当前城市。
- 如何按天气条件映射颜色和图标。
- 如何在卡片中展示主天气和辅助指标。
- 如何面向鸿蒙验证搜索、滚动和卡片布局。
二、工程结构与运行方式
2.1 工程结构
项目保持标准 Flutter 工程结构,核心代码集中在 lib/main.dart:
| 文件或目录 | 作用 | 说明 |
|---|---|---|
lib/main.dart |
应用入口与页面实现 | 包含天气数据、搜索和列表逻辑 |
pubspec.yaml |
依赖声明 | 使用 Flutter SDK 与 Material 图标 |
test/widget_test.dart |
Widget 测试入口 | 可扩展为天气业务测试 |
ohos/ |
鸿蒙平台工程目录 | 用于跨端构建和适配 |
2.2 依赖声明
项目没有引入复杂第三方依赖:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
这说明天气展示、城市筛选和交互逻辑都由 Flutter 与 Dart 完成,不依赖联网接口。
2.3 常用命令
开发和验证时可以使用以下命令:
flutter pub get
flutter analyze
flutter test
flutter run
| 命令 | 作用 | 使用场景 |
|---|---|---|
flutter pub get |
获取依赖 | 首次运行或依赖变化 |
flutter analyze |
静态分析 | 检查语法和 lint |
flutter test |
执行测试 | 验证 Widget 行为 |
flutter run |
启动应用 | 本地调试界面 |
三、应用入口与主题配置
3.1 main 函数
应用入口保持 Flutter 标准写法:
void main() {
runApp(const MyApp());
}
天气演示应用不需要启动时加载网络数据,因此入口很简洁。
3.2 MyApp 根组件
根组件负责创建 MaterialApp:
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Weather',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.lightBlue),
),
home: const MyHomePage(title: 'Weather'),
);
}
}
这里有三个关键信息:
- 应用标题是
Weather。 - 主题种子色是
Colors.lightBlue。 - 首页是
MyHomePage。
3.3 主题色选择
浅蓝色很适合天气类应用,能和天空、空气、晴朗等视觉联想建立联系。
当前源码没有显式设置
useMaterial3: true,因此文章以真实代码为准。如果后续需要统一 Material 3 表现,可以在ThemeData中补充该配置。
四、状态字段设计
4.1 核心状态总览
页面状态集中在 _MyHomePageState 中:
String _selectedCity = 'New York';
String _searchQuery = '';
bool _isLoading = false;
final List<Map<String, dynamic>> _cities = [
{'name': 'New York', 'temp': 22, 'condition': 'Sunny', 'humidity': 65, 'wind': 12, 'icon': Icons.wb_sunny},
{'name': 'London', 'temp': 15, 'condition': 'Cloudy', 'humidity': 80, 'wind': 8, 'icon': Icons.cloud},
];
final TextEditingController _searchController = TextEditingController();
4.2 字段说明
| 字段 | 类型 | 初始值 | 作用 |
|---|---|---|---|
_selectedCity |
String |
New York |
当前选中城市 |
_searchQuery |
String |
空字符串 | 搜索过滤关键词 |
_isLoading |
bool |
false |
预留加载状态 |
_cities |
List<Map<String, dynamic>> |
6 个城市 | 城市天气数据 |
_searchController |
TextEditingController |
空 | 搜索输入控制器 |
4.3 状态分层
| 状态类别 | 字段 | 说明 |
|---|---|---|
| 当前展示 | _selectedCity |
主卡片显示哪座城市 |
| 过滤状态 | _searchQuery |
列表过滤条件 |
| 数据状态 | _cities |
城市天气集合 |
| 输入状态 | _searchController |
搜索框文本 |
4.4 预留字段
_isLoading 在当前源码中没有实际使用,属于预留状态字段。它可能是为后续接入网络天气接口做准备,但在现版本中没有参与 UI 逻辑。
五、控制器生命周期管理
5.1 搜索控制器
顶部搜索框使用一个控制器:
final TextEditingController _searchController = TextEditingController();
5.2 dispose 释放资源
页面销毁时释放控制器:
void dispose() {
_searchController.dispose();
super.dispose();
}
5.3 生命周期表
| 阶段 | 行为 |
|---|---|
| 页面创建 | 创建搜索控制器 |
| 用户输入 | 控制器保存搜索词 |
| 页面销毁 | 释放控制器 |
六、当前城市天气查询
6.1 currentWeather getter
当前城市天气通过 _currentWeather 获取:
Map<String, dynamic> get _currentWeather {
return _cities.firstWhere(
(city) => city['name'] == _selectedCity,
orElse: () => _cities[0],
);
}
6.2 firstWhere 的作用
firstWhere 会按条件查找第一个匹配项。如果没有找到,则回退到列表第一个城市。
6.3 当前城市与天气卡片
主卡片中的温度、天气状况、湿度和风速都来自 _currentWeather。
6.4 兜底策略
| 情况 | 行为 |
|---|---|
| 找到选中城市 | 显示该城市天气 |
| 未找到选中城市 | 回退到列表第一项 |
七、搜索过滤逻辑
7.1 searchCity 方法
搜索词由 _searchCity() 更新:
void _searchCity(String query) {
setState(() {
_searchQuery = query.toLowerCase();
});
}
7.2 小写匹配
这里把搜索词转成小写:
query.toLowerCase()
这样能让搜索匹配不区分大小写。
7.3 过滤条件
城市列表展示时使用:
_cities.where((city) =>
city['name'] != _selectedCity &&
city['name'].toLowerCase().contains(_searchQuery)
)
7.4 过滤规则表
| 条件 | 结果 |
|---|---|
| 名称等于当前城市 | 不显示在 Other Cities |
| 名称包含搜索词 | 显示 |
| 名称不匹配搜索词 | 不显示 |
7.5 过滤体验
用户输入搜索词后,下方其他城市列表会实时缩小范围,便于快速定位城市。
八、天气条件配色
8.1 getWeatherColor 方法
天气颜色由 _getWeatherColor() 决定:
Color _getWeatherColor(String condition) {
switch (condition.toLowerCase()) {
case 'sunny':
case 'clear':
return Colors.orange;
case 'cloudy':
case 'partly cloudy':
return Colors.blueGrey;
case 'rainy':
return Colors.blue;
case 'hot':
return Colors.red;
default:
return Colors.lightBlue;
}
}
8.2 条件映射表
| 天气条件 | 颜色 |
|---|---|
| Sunny / Clear | orange |
| Cloudy / Partly Cloudy | blueGrey |
| Rainy | blue |
| Hot | red |
| 其他 | lightBlue |
8.3 颜色使用位置
天气颜色会用于:
- 主卡片背景淡色。
- 主温度大字号。
- 当前天气描述文本。
- 城市列表图标颜色。
8.4 视觉价值
颜色让用户不必读完文字,也能快速感知天气类型,比如橙色表示晴朗,蓝色表示雨天,红色表示高温。
九、天气图标映射
9.1 图标数据
每个城市数据里都预置了图标:
{'name': 'New York', 'temp': 22, 'condition': 'Sunny', 'humidity': 65, 'wind': 12, 'icon': Icons.wb_sunny}
9.2 图标使用方式
主天气卡片和列表项都会使用城市图标:
Icon(weather['icon'], size: 80, color: weatherColor)
leading: Icon(city['icon'], color: _getWeatherColor(city['condition']))
9.3 图标语义
| 图标 | 含义 |
|---|---|
wb_sunny |
晴天 |
cloud |
多云 |
grain |
雨天 |
cloud_queue |
局部多云 |
wb_sunny + Hot |
高温晴热 |
9.4 图标与颜色协同
图标和颜色一起构成天气语义,减少用户理解成本。
十、主天气卡片布局
10.1 主卡片结构
主天气信息放在一张大卡片里:
Card(
color: weatherColor.withAlpha(30),
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(...),
),
)
10.2 城市标题
标题区域展示地点图标和城市名:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.location_on, size: 20),
const SizedBox(width: 4),
Text(_selectedCity),
],
)
10.3 温度展示
温度使用超大字号:
Text(
'${weather['temp']}°C',
style: TextStyle(fontSize: 72, fontWeight: FontWeight.bold, color: weatherColor),
)
10.4 状态文本
天气描述文本如下:
Text(weather['condition'])
10.5 卡片作用
| 元素 | 作用 |
|---|---|
| 城市名 | 当前查看对象 |
| 温度 | 当前核心数据 |
| 天气描述 | 当前天气状态 |
| 图标和颜色 | 增强视觉识别 |
十一、湿度与风速卡片
11.1 双卡片布局
湿度和风速使用一行双卡片:
Row(
children: [
Expanded(child: Card(...)),
const SizedBox(width: 8),
Expanded(child: Card(...)),
],
)
11.2 湿度卡片
Text('${weather['humidity']}%')
11.3 风速卡片
Text('${weather['wind']} km/h')
11.4 指标对比表
| 指标 | 单位 | 作用 |
|---|---|---|
| 温度 | °C | 主天气感知 |
| 湿度 | % | 体感辅助 |
| 风速 | km/h | 风感辅助 |
十二、其他城市列表
12.1 Other Cities 标题
页面下方列表标题为:
const Text('Other Cities')
12.2 列表生成
其他城市列表通过过滤后的映射生成:
..._cities.where(...).map((city) {
return Card(
child: ListTile(...),
);
})
12.3 列表内容
每个列表项展示:
- 左侧天气图标。
- 中间城市名和天气描述。
- 右侧温度。
12.4 当前城市排除
列表过滤时会排除当前选中的城市,因此当前查看城市只在主卡片展示,不会重复出现在下方列表。
十三、点击城市切换
13.1 切换逻辑
点击列表项时会更新当前城市:
onTap: () {
setState(() {
_selectedCity = city['name'];
_searchController.clear();
_searchCity('');
});
}
13.2 切换后清空搜索
切换城市后会清空搜索框并重置搜索词,避免列表继续停留在旧筛选状态。
13.3 交互闭环
点击城市
-> 更新 selectedCity
-> 清空搜索框
-> 重置 searchQuery
-> 主天气卡片刷新
-> 其他城市列表刷新
13.4 体验说明
这个设计让用户可以先搜索城市,再点选城市,随后界面回到完整列表状态。
十四、鸿蒙适配关注点
14.1 为什么适配风险较低
weather 主要由 Flutter 标准组件和 Dart 状态逻辑构成,不依赖网络、定位、数据库、系统天气接口或平台通道,因此基础适配风险较低。
| 模块 | 是否依赖平台能力 | 适配关注度 |
|---|---|---|
| 搜索输入 | Flutter 标准输入 | 中 |
| 城市列表 | Flutter 标准列表 | 低 |
| 天气卡片 | Flutter 标准卡片 | 低 |
| 图标与颜色 | Flutter 标准图标 | 低 |
| 网络天气 | 当前未实现 | 高 |
14.2 输入法与软键盘
鸿蒙设备上需要重点验证:
- 搜索框是否正常弹出键盘。
- 输入后列表是否实时过滤。
- 清空按钮是否正常工作。
- 键盘弹出后主卡片是否被遮挡。
14.3 列表滚动
底部 Other Cities 使用列表展示,城市数量增加时要观察滚动是否顺滑,长城市名是否溢出。
14.4 数据边界
当前项目只是本地天气演示,所有城市数据都写在 _cities 中,并不是联网实时天气。如果要做正式天气应用,需要接入真实天气 API。
当前项目更适合作为 Flutter 天气 UI 和搜索过滤样例,不应理解为完整的实时天气服务。
十五、测试设计与默认测试改造
15.1 当前测试入口
项目中的测试文件仍是默认计数器测试。对于天气应用,更有价值的是验证初始城市、搜索过滤、城市切换和卡片渲染。
15.2 初始页面测试
testWidgets('weather app renders initial state', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
expect(find.text('Weather'), findsWidgets);
expect(find.text('New York'), findsOneWidget);
expect(find.text('Humidity'), findsOneWidget);
expect(find.text('Wind'), findsOneWidget);
});
15.3 搜索框测试
testWidgets('can type into search box', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
await tester.enterText(find.byType(TextField), 'Tokyo');
await tester.pump();
expect(find.text('Tokyo'), findsWidgets);
});
15.4 城市切换测试
testWidgets('can select a city from list', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
await tester.tap(find.text('London'));
await tester.pump();
expect(find.text('London'), findsWidgets);
});
15.5 天气颜色测试建议
如果把 _getWeatherColor() 抽成纯函数,可以直接测试不同条件下的颜色映射。
十六、可维护性优化方向
16.1 抽离天气模型
当前使用 Map 保存城市天气,简洁但类型不够明确。可以抽成模型:
class WeatherCity {
const WeatherCity({
required this.name,
required this.temp,
required this.condition,
required this.humidity,
required this.wind,
required this.icon,
});
final String name;
final int temp;
final String condition;
final int humidity;
final int wind;
final IconData icon;
}
16.2 增加真实接口
如果后续接入网络天气 API,可以把 _cities 替换为接口返回的数据,并将 _isLoading 用于加载态。
16.3 支持收藏城市
可以加入收藏城市、置顶城市或常看城市,让天气工具更实用。
16.4 支持刷新
当前项目没有手动刷新按钮。若接入实时天气数据,可以增加下拉刷新或顶部刷新按钮。
十七、功能扩展方向
17.1 增加真实天气接口
接入 OpenWeather、WeatherAPI 或其他天气服务后,就可以把本地演示升级为实时天气工具。
17.2 增加小时预报
在主卡片下方展示未来几个小时的温度变化,会更接近正式天气应用。
17.3 增加每日预报
可以扩展为 5 天或 7 天预报,让页面更完整。
17.4 增加位置定位
后续可以加入定位能力,自动显示用户当前城市。
十八、常见问题与优化建议
18.1 为什么搜索框是本地过滤
因为源码中没有网络请求。搜索只是通过字符串匹配过滤 _cities。
18.2 为什么城市列表会排除当前城市
这样当前城市只在主卡片显示,避免重复。
18.3 为什么天气颜色不是统一色
不同天气条件需要不同视觉语义,颜色能帮助用户快速理解天气类型。
18.4 为什么没有加载动画
源码中虽然有 _isLoading 字段,但当前没有接入联网数据,所以没有真正使用加载态。
18.5 鸿蒙适配最应该关注什么
重点关注搜索输入、列表滚动、卡片排版、长城市名显示和软键盘遮挡。当前项目没有联网能力,也没有实时天气刷新。
十九、完整流程复盘
19.1 页面启动流程
main()
-> runApp(MyApp)
-> MaterialApp
-> MyHomePage
-> 初始化城市列表
-> build 渲染主卡片和列表
19.2 搜索流程
输入城市名
-> _searchCity
-> 小写关键词更新
-> 过滤 Other Cities
-> 页面刷新
19.3 城市切换流程
点击列表项
-> 更新 selectedCity
-> 清空搜索
-> 重置搜索词
-> 主卡片刷新
19.4 视觉计算流程
读取当前城市天气
-> 计算颜色
-> 计算图标
-> 显示温度、天气、湿度和风速
二十、相关资源与继续学习
20.1 Flutter 学习资源
天气应用涉及输入、列表、卡片和测试,可以结合以下资源学习:
| 资源 | 内容 |
|---|---|
| Flutter Docs | Flutter 官方开发文档 |
| Dart 官方文档 | Dart 语言与核心库 |
| Widget catalog | Flutter 常用组件 |
| Flutter testing | Widget 测试与交互模拟 |
20.2 天气应用扩展方向
后续可以继续增强:
- 真实天气接口。
- 用户定位。
- 小时预报。
- 未来几天预报。
- 收藏城市。
- 刷新按钮。
- 深色模式。
- 单位切换。
20.3 跨端实践价值
weather 很适合作为 Flutter 适配鸿蒙的小型天气样例。它依赖很轻,但覆盖了搜索输入、列表过滤、卡片展示、图标颜色映射和滚动布局,能帮助开发者验证很多跨端基础能力。
总结
weather 用简洁的 Flutter 代码实现了一个本地天气演示应用。它通过 _cities 保存城市天气数据,通过 _selectedCity 控制当前展示城市,通过 _searchQuery 实现搜索过滤,通过 _getWeatherColor() 和城市数据中的 icon 字段提供天气语义化展示,并在列表中支持切换城市。
从工程角度看,这个项目最值得学习的是“本地天气数据 + 搜索过滤 + 主卡片展示”的组合方式。面向鸿蒙适配时,项目依赖较轻,主要需要验证搜索输入、列表滚动、卡片布局和触摸反馈。对于想学习 Flutter 天气类 UI 的开发者来说,它是一个清晰、实用且容易扩展的案例。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
更多推荐



所有评论(0)