# Flutter 三方库 鸿蒙天气日历助手项目实践:基于 AP120 与鸿蒙 6.0+ 的跨端开发案例
Flutter 三方库 鸿蒙天气日历助手项目实践:基于 AP12 与鸿蒙 6.0+ 的跨端开发案例
一、文章简介
本文是一篇面向 Flutter 跨端开发场景的 Flutter + 三方库 + 鸿蒙 项目实践文档。文中以 天气日历助手 为案例,完整展示一个天气信息展示项目的实现过程。这个项目可以完成以下功能:
- 展示今日日期和当前城市
- 通过三方库获取网络天气数据
- 使用卡片形式展示天气信息
- 支持下拉刷新
- 用于说明 Flutter 跨端开发在鸿蒙设备上的基础实现方式
这篇文章的目标不是追求复杂业务,而是帮助鸿蒙开发者快速理解:
- Flutter 项目怎么创建
- 三方库怎么接入
- 页面怎么组织
- 网络请求怎么写
- 数据模型怎么设计
- 如何把一个 Flutter 应用作为跨端项目思路迁移到鸿蒙场景
二、案例名称
项目名称:天气日历助手
这个项目的实践意义主要体现在以下三个方面:
- 页面结构简单,便于理解 Flutter 组件化开发
- 会用到常见三方库,具有真实项目意义
- 同时覆盖 UI、网络请求、状态更新、列表渲染等核心知识点
三、为什么选择这个案例
在 Flutter 入门阶段,开发者通常会遇到以下两个问题:
- 只会写静态页面,不会做实际功能
- 不知道三方库在项目中怎么接入和使用
而对于鸿蒙开发者来说,还多一个问题:
- 已经会原生开发,但不知道 Flutter 跨端项目的基本结构
所以这里选择“天气日历助手”作为案例,能够让你一次练到这些核心点:
http:发送网络请求intl:处理日期格式化pull_to_refresh:实现下拉刷新
这三个都是 Flutter 项目中较常见的三方库,案例规模适中、展示效果直观,能够覆盖基础开发中的关键能力点。
四、项目效果说明
本项目完成后,页面会显示:
- 顶部标题:天气日历助手
- 当前日期
- 当前城市名称
- 今日气温
- 天气描述
- 风力信息
- 一个“刷新天气”按钮
- 支持列表下拉刷新
该页面属于标准的信息展示类界面,用于说明 Flutter 在鸿蒙设备上的基础页面构建与数据交互流程。
五、环境准备
在开始之前,你需要准备以下环境。本文默认的目标运行环境为:
HarmonyOS 6.0及以上版本API Version 12,也就是AP120,或更高版本 SDK
也就是说,本文案例不是面向旧版本鸿蒙环境,而是面向 鸿蒙 6.0+ / AP120+ 的开发与实践场景。
1. 安装 Flutter SDK
先确认本机已经安装 Flutter:
flutter --version
如果终端能够输出 Flutter 版本号,说明安装成功。
2. 配置开发工具
准备支持 Flutter 与 Dart 开发的编辑环境,并确保命令行可以正常执行 flutter 相关命令。
3. 配置鸿蒙 6.0+ 开发环境
如果你要把这个项目放到鸿蒙场景中实践,建议明确以下基础条件:
- DevEco Studio 中安装
AP120或以上版本 SDK - 目标设备系统版本为
HarmonyOS 6.0或以上 - 已具备 Flutter 对接鸿蒙的运行或适配环境
这样做的原因是:不同鸿蒙版本在 SDK 能力、工程配置和运行支持上会有差异。本文为了避免环境混乱,统一以 AP120+ 与鸿蒙 6.0+ 作为实践前提。
4. 准备鸿蒙设备或模拟环境
如果你要把这个项目放到鸿蒙场景中实践,通常需要准备:
- 鸿蒙 6.0 及以上设备
- 或者对应的运行环境
- 以及你当前所使用的 Flutter 鸿蒙适配方案
说明:不同阶段的 Flutter 对鸿蒙的适配方式可能不同。本文重点放在 Flutter 项目开发实践本身,也就是页面、逻辑、三方库接入和代码组织。运行本文案例时,请确保你的鸿蒙工程 SDK 版本不低于
AP120,系统版本不低于HarmonyOS 6.0。
六、创建 Flutter 项目
打开终端,执行下面命令:
flutter create weather_mate
进入项目目录:
cd weather_mate
运行项目:
flutter run
如果看到默认计数器页面,说明项目创建成功。
七、添加三方库
打开项目根目录下的 pubspec.yaml 文件,在 dependencies 中加入以下内容:
dependencies:
flutter:
sdk: flutter
http: ^1.2.1
intl: ^0.19.0
pull_to_refresh: ^2.0.0
三方库作用说明
1. http
用于发送 HTTP 网络请求,从天气接口获取数据。
2. intl
用于格式化日期,比如把当前时间显示成 2026年04月08日 星期三 这种更友好的格式。
3. pull_to_refresh
用于给页面增加下拉刷新能力。
添加完成后执行:
flutter pub get
这一步会下载依赖包。
八、项目目录设计
为了让代码结构更清晰,我们可以把项目拆分成下面几个部分:
lib/
├── main.dart
├── models/
│ └── weather_info.dart
├── pages/
│ └── weather_home_page.dart
└── services/
└── weather_service.dart
为什么这样拆分
main.dart:应用入口models:数据模型层pages:页面层services:服务层,专门处理网络请求
这是一种较为基础但实用的 Flutter 项目结构,能够较好地支持页面、模型和服务逻辑的拆分。
九、天气接口说明
本项目采用标准 HTTP JSON 接口方式获取天气数据。为了保持文档的通用性,下面使用占位形式的业务接口路径进行说明:
/api/weather?city=Beijing
接口返回的数据格式约定如下:
{
"current_condition": [
{
"temp_C": "26",
"weatherDesc": [
{
"value": "Sunny"
}
],
"windspeedKmph": "12"
}
]
}
在实际项目中,只要接口能够返回结构相近的 JSON 数据,就可以复用本文中的解析逻辑。
十、开始写代码
下面我们一步一步完成整个项目。
十一、编写数据模型
新建文件:lib/models/weather_info.dart
class WeatherInfo {
final String city;
final String temperature;
final String weatherDesc;
final String windSpeed;
WeatherInfo({
required this.city,
required this.temperature,
required this.weatherDesc,
required this.windSpeed,
});
// 工厂构造函数:把接口返回的 JSON 数据解析成 WeatherInfo 对象
factory WeatherInfo.fromJson(Map<String, dynamic> json, String cityName) {
final currentCondition = json['current_condition'][0];
return WeatherInfo(
city: cityName,
temperature: currentCondition['temp_C'] ?? '0',
weatherDesc: currentCondition['weatherDesc'][0]['value'] ?? '未知天气',
windSpeed: currentCondition['windspeedKmph'] ?? '0',
);
}
}
代码解释
1. WeatherInfo 是什么
这是一个数据模型类,用来统一保存天气信息。
2. 为什么要建模型类
因为接口返回的是原始 JSON,如果直接在页面里乱写取值逻辑,代码会很乱。把数据先整理成一个对象,页面就会更清晰。
3. factory WeatherInfo.fromJson
这是一个工厂构造方法,用来把接口返回的数据转换成 Dart 对象。
十二、编写网络请求服务
新建文件:lib/services/weather_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/weather_info.dart';
class WeatherService {
// 获取指定城市的天气信息
Future<WeatherInfo> fetchWeather(String city) async {
final url = Uri.parse('https://your-server-address/api/weather?city=$city');
// 发送 GET 请求
final response = await http.get(url);
// 判断请求是否成功
if (response.statusCode == 200) {
final Map<String, dynamic> jsonData = json.decode(response.body);
// 把 JSON 数据转换成 WeatherInfo 对象
return WeatherInfo.fromJson(jsonData, city);
} else {
// 如果接口请求失败,主动抛出异常,方便页面层捕获
throw Exception('天气数据获取失败');
}
}
}
代码解释
1. http.get(url)
用于发起网络请求。
2. json.decode(response.body)
把字符串类型的 JSON 转成 Dart 中的 Map 对象。
3. throw Exception(...)
表示请求失败时主动抛出错误,后续页面里可以捕获并提示用户。
十三、编写主页面
新建文件:lib/pages/weather_home_page.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../models/weather_info.dart';
import '../services/weather_service.dart';
class WeatherHomePage extends StatefulWidget {
const WeatherHomePage({super.key});
State<WeatherHomePage> createState() => _WeatherHomePageState();
}
class _WeatherHomePageState extends State<WeatherHomePage> {
final WeatherService _weatherService = WeatherService();
final RefreshController _refreshController =
RefreshController(initialRefresh: false);
WeatherInfo? weatherInfo;
bool isLoading = true;
String errorText = '';
final String cityName = 'Beijing';
void initState() {
super.initState();
_loadWeather();
}
// 加载天气数据
Future<void> _loadWeather() async {
try {
final data = await _weatherService.fetchWeather(cityName);
setState(() {
weatherInfo = data;
isLoading = false;
errorText = '';
});
} catch (e) {
setState(() {
isLoading = false;
errorText = '加载失败:$e';
});
}
}
// 下拉刷新逻辑
Future<void> _onRefresh() async {
await _loadWeather();
_refreshController.refreshCompleted();
}
void dispose() {
_refreshController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
// 使用 intl 三方库格式化日期
final String currentDate =
DateFormat('yyyy年MM月dd日 EEEE', 'zh_CN').format(DateTime.now());
return Scaffold(
appBar: AppBar(
title: const Text('天气日历助手'),
centerTitle: true,
backgroundColor: Colors.lightBlue,
),
body: SmartRefresher(
controller: _refreshController,
onRefresh: _onRefresh,
enablePullDown: true,
child: _buildBody(currentDate),
),
);
}
Widget _buildBody(String currentDate) {
if (isLoading) {
// 数据加载中时显示进度条
return const Center(
child: CircularProgressIndicator(),
);
}
if (errorText.isNotEmpty) {
// 加载失败时显示错误信息
return Center(
child: Text(
errorText,
style: const TextStyle(fontSize: 16, color: Colors.red),
),
);
}
if (weatherInfo == null) {
// 理论上的兜底分支,避免空对象导致页面崩溃
return const Center(
child: Text('暂无天气数据'),
);
}
return ListView(
padding: const EdgeInsets.all(16),
children: [
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'今日天气',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text(
'日期:$currentDate',
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 8),
Text(
'城市:${weatherInfo!.city}',
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 8),
Text(
'温度:${weatherInfo!.temperature}℃',
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 8),
Text(
'天气:${weatherInfo!.weatherDesc}',
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 8),
Text(
'风速:${weatherInfo!.windSpeed} km/h',
style: const TextStyle(fontSize: 16),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _loadWeather,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.lightBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: const Text('刷新天气'),
),
),
],
),
),
),
],
);
}
}
代码解释
1. 为什么使用 StatefulWidget
因为天气数据是动态加载的,页面内容会随着请求结果变化,所以这里使用有状态组件。
2. initState() 做了什么
页面一打开就调用 _loadWeather(),自动获取天气数据。
3. setState() 的作用
当数据请求完成后,调用 setState() 可以通知 Flutter 重新构建界面。
4. SmartRefresher
这是 pull_to_refresh 三方库提供的组件,用于实现下拉刷新功能。
5. _buildBody()
把页面主体拆成独立方法,能让 build() 更简洁。
十四、修改应用入口文件
打开 lib/main.dart,替换成下面代码:
import 'package:flutter/material.dart';
import 'pages/weather_home_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: '天气日历助手',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const WeatherHomePage(),
);
}
}
代码解释
1. runApp(const MyApp())
这是 Flutter 应用启动入口。
2. MaterialApp
它是一个 Material Design 风格应用的根组件,负责应用主题、路由、标题等配置。
3. home: const WeatherHomePage()
表示应用启动后默认进入天气首页。
十五、完整代码汇总
为了便于统一查看项目结构,这里对关键文件进行一次汇总。
1. lib/main.dart
import 'package:flutter/material.dart';
import 'pages/weather_home_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: '天气日历助手',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const WeatherHomePage(),
);
}
}
2. lib/models/weather_info.dart
class WeatherInfo {
final String city;
final String temperature;
final String weatherDesc;
final String windSpeed;
WeatherInfo({
required this.city,
required this.temperature,
required this.weatherDesc,
required this.windSpeed,
});
factory WeatherInfo.fromJson(Map<String, dynamic> json, String cityName) {
final currentCondition = json['current_condition'][0];
return WeatherInfo(
city: cityName,
temperature: currentCondition['temp_C'] ?? '0',
weatherDesc: currentCondition['weatherDesc'][0]['value'] ?? '未知天气',
windSpeed: currentCondition['windspeedKmph'] ?? '0',
);
}
}
3. lib/services/weather_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/weather_info.dart';
class WeatherService {
Future<WeatherInfo> fetchWeather(String city) async {
final url = Uri.parse('https://your-server-address/api/weather?city=$city');
final response = await http.get(url);
if (response.statusCode == 200) {
final Map<String, dynamic> jsonData = json.decode(response.body);
return WeatherInfo.fromJson(jsonData, city);
} else {
throw Exception('天气数据获取失败');
}
}
}
4. lib/pages/weather_home_page.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import '../models/weather_info.dart';
import '../services/weather_service.dart';
class WeatherHomePage extends StatefulWidget {
const WeatherHomePage({super.key});
State<WeatherHomePage> createState() => _WeatherHomePageState();
}
class _WeatherHomePageState extends State<WeatherHomePage> {
final WeatherService _weatherService = WeatherService();
final RefreshController _refreshController =
RefreshController(initialRefresh: false);
WeatherInfo? weatherInfo;
bool isLoading = true;
String errorText = '';
final String cityName = 'Beijing';
void initState() {
super.initState();
_loadWeather();
}
Future<void> _loadWeather() async {
try {
final data = await _weatherService.fetchWeather(cityName);
setState(() {
weatherInfo = data;
isLoading = false;
errorText = '';
});
} catch (e) {
setState(() {
isLoading = false;
errorText = '加载失败:$e';
});
}
}
Future<void> _onRefresh() async {
await _loadWeather();
_refreshController.refreshCompleted();
}
void dispose() {
_refreshController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
final String currentDate =
DateFormat('yyyy年MM月dd日 EEEE', 'zh_CN').format(DateTime.now());
return Scaffold(
appBar: AppBar(
title: const Text('天气日历助手'),
centerTitle: true,
backgroundColor: Colors.lightBlue,
),
body: SmartRefresher(
controller: _refreshController,
onRefresh: _onRefresh,
enablePullDown: true,
child: _buildBody(currentDate),
),
);
}
Widget _buildBody(String currentDate) {
if (isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (errorText.isNotEmpty) {
return Center(
child: Text(
errorText,
style: const TextStyle(fontSize: 16, color: Colors.red),
),
);
}
if (weatherInfo == null) {
return const Center(
child: Text('暂无天气数据'),
);
}
return ListView(
padding: const EdgeInsets.all(16),
children: [
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'今日天气',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
Text('日期:$currentDate', style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Text('城市:${weatherInfo!.city}',
style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Text('温度:${weatherInfo!.temperature}℃',
style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Text('天气:${weatherInfo!.weatherDesc}',
style: const TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Text('风速:${weatherInfo!.windSpeed} km/h',
style: const TextStyle(fontSize: 16)),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _loadWeather,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.lightBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: const Text('刷新天气'),
),
),
],
),
),
),
],
);
}
}
十六、如何运行项目
1. 拉取依赖
flutter pub get
2. 启动项目
flutter run
3. 在鸿蒙环境中运行
你可以按自己的 Flutter 鸿蒙适配流程进行以下操作:
- 准备鸿蒙运行环境
- 导入 Flutter 项目
- 完成平台适配
- 部署到鸿蒙设备
- 观察页面显示和网络请求是否正常
本文案例的重点是:先掌握 Flutter 跨端项目的开发过程,再映射到 AP120+、鸿蒙 6.0+ 的设备运行环境中。
十七、项目中学到了什么
完成这个案例之后,你已经掌握了以下内容:
- 如何创建 Flutter 项目
- 如何接入三方库
- 如何发送网络请求
- 如何解析 JSON 数据
- 如何封装数据模型
- 如何使用
StatefulWidget管理页面状态 - 如何实现下拉刷新
- 如何组织一个简单的 Flutter 项目目录
这些能力已经足够支撑你继续做更复杂的 Flutter 跨端项目。
十八、项目优化方向
在当前实现基础上,项目还可以继续向以下方向扩展:
1. 增加城市切换功能
可以让用户输入城市名,动态查询不同地区天气。
2. 增加天气图标
可以根据天气状态显示晴天、阴天、雨天等图标。
3. 增加未来天气预报
除了显示今天,还可以显示未来 3 天或 7 天数据。
4. 接入本地存储
可以把上一次查询的城市保存下来,下次打开直接读取。
5. 做成更标准的跨端项目
后续可以继续研究:
- Flutter 与鸿蒙平台交互
- 插件适配
- 原生能力调用
- 跨端 UI 一致性处理
十九、项目总结
本文通过“天气日历助手”这一具体案例,完成了一个 Flutter 跨端项目的基础实践。项目接入了 http、intl、pull_to_refresh 等三方库,实现了天气查询、日期展示、状态刷新等功能。本文以 AP120 及以上 SDK、HarmonyOS 6.0 及以上系统版本为目标环境,完整展示了从项目创建、依赖接入、网络请求、数据建模到页面渲染的实现过程。
二十、全文结论
对于鸿蒙开发场景而言,Flutter 的学习和实践应当落在具体项目上,而不仅仅停留在概念层面。本文的“天气日历助手”项目虽然体量较小,但已经覆盖了 Flutter 入门阶段较为核心的知识点。
基于这一案例,可以进一步扩展到待办清单、新闻阅读器、记账应用、地图定位工具等更复杂的跨端项目,并逐步深入到 Flutter 与鸿蒙平台交互、插件适配和原生能力调用等方向。
二十一、文档标题
Flutter 三方库 鸿蒙天气日历助手项目实践:基于 AP120 与鸿蒙 6.0+ 的跨端开发案例
更多推荐

所有评论(0)