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 实战能力:

  1. 如何用 List<Map<String, dynamic>> 保存城市天气数据。
  2. 如何通过搜索词过滤城市列表。
  3. 如何点击列表项切换当前城市。
  4. 如何按天气条件映射颜色和图标。
  5. 如何在卡片中展示主天气和辅助指标。
  6. 如何面向鸿蒙验证搜索、滚动和卡片布局。

二、工程结构与运行方式

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 的开发者来说,它是一个清晰、实用且容易扩展的案例。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐