news 2026/9/29 2:07:23

# Flutter实战:打造一个天气预报应用(附完整代码与界面图)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
# Flutter实战:打造一个天气预报应用(附完整代码与界面图)

## 🌤️ 引言

在本篇文章中,我们将使用 **Flutter** 构建一个跨平台的 **天气预报应用**。它将通过调用免费的天气 API 获取实时天气数据,并以美观的卡片形式展示给用户。

我们将涵盖以下关键技术点:

- 网络请求(`http` 包)
- JSON 数据解析
- 异步编程(`Future` 与 `async/await`)
- 状态管理基础
- 自定义 UI 组件

最终效果如下:

![天气应用效果图](https://i.imgur.com/ZKfFwRr.png)

*图:运行在 Android 上的 Flutter 天气应用*

---

## 🔧 准备工作

### 1. 创建项目

```bash
flutter create weather_app
cd weather_app
```

### 2. 添加依赖

编辑 `pubspec.yaml` 文件,添加网络请求库:

```yaml
dependencies:
flutter:
sdk: flutter
http: ^0.16.0 # 用于发送HTTP请求
```

然后运行:

```bash
flutter pub get
```

### 3. 获取天气 API 密钥

我们使用 [OpenWeatherMap](https://openweathermap.org/api) 的免费 API。

注册后获取你的 `API Key`,例如:`your_api_key_here`

> 🔐 提示:不要将密钥硬编码在代码中!此处为教学简化处理。

---

## 🗺️ 定义城市与天气模型

创建文件 `lib/models/weather.dart`:

```dart
class Weather {
final String city;
final double temperature;
final String description;
final String iconCode;

Weather({
required this.city,
required this.temperature,
required this.description,
required this.iconCode,
});

// 工厂构造函数:从 JSON 创建对象
factory Weather.fromJson(Map<String, dynamic> json) {
return Weather(
city: json['name'],
temperature: json['main']['temp'].toDouble(),
description: json['weather'][0]['description'],
iconCode: json['weather'][0]['icon'],
);
}
}
```

---

## 🌐 调用天气 API

创建 `lib/services/weather_service.dart`:

```dart
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/weather.dart';

class WeatherService {
static const String baseUrl = 'https://api.openweathermap.org/data/2.5/weather';
static const String apiKey = 'your_api_key_here'; // 替换为你自己的Key

Future<Weather> getWeather(String cityName) async {
final response = await http.get(Uri.parse(
'$baseUrl?q=$cityName&appid=$apiKey&units=metric&lang=zh_cn'));

if (response.statusCode == 200) {
final data = json.decode(response.body);
return Weather.fromJson(data);
} else {
throw Exception('Failed to load weather');
}
}
}
```

---

## 🎨 构建主页面 UI

替换 `lib/main.dart` 内容:

```dart
import 'package:flutter/material.dart';
import 'package:weather_app/models/weather.dart';
import 'package:weather_app/services/weather_service.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter 天气预报',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const WeatherPage(),
debugShowCheckedModeBanner: false,
);
}
}

class WeatherPage extends StatefulWidget {
const WeatherPage({super.key});

@override
State<WeatherPage> createState() => _WeatherPageState();
}

class _WeatherPageState extends State<WeatherPage> {
final TextEditingController _controller = TextEditingController();
Weather? _weather;
bool _loading = false;
final WeatherService _service = WeatherService();

@override
void dispose() {
_controller.dispose();
super.dispose();
}

Future<void> _fetchWeather() async {
if (_controller.text.isEmpty) return;

setState(() {
_loading = true;
});

try {
final weather = await _service.getWeather(_controller.text);
setState(() {
_weather = weather;
_loading = false;
});
} catch (e) {
setState(() {
_loading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('错误: $e')),
);
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('🌤️ 天气预报'),
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: '输入城市名称',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _fetchWeather(),
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.search),
onPressed: _fetchWeather,
),
],
),
const SizedBox(height: 20),

// 加载状态
if (_loading)
const CircularProgressIndicator()
else if (_weather != null)
Expanded(
child: Card(
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Text(
'${_weather!.city}',
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Image.network(
'https://openweathermap.org/img/wn/${_weather!.iconCode}@2x.png',
width: 100,
),
Text(
'${_weather!.temperature.toStringAsFixed(1)}°C',
style: const TextStyle(
fontSize: 40,
color: Colors.orange,
),
),
Text(
'${_weather!.description}'.toUpperCase(),
style: const TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
),
),
),
)
else
const Expanded(
child: Center(
child: Text(
'请输入城市名称查询天气',
style: TextStyle(fontSize: 18, color: Colors.grey),
),
),
),
],
),
),
);
}
}
```

---

## 🖼️ 运行效果展示

### 启动应用

```bash
flutter run
```

### 查询“北京”天气

![查询北京天气](https://i.imgur.com/9mXWYvP.png)

*图:成功显示北京当前天气*

### 错误提示示例

如果城市不存在,会弹出提示:

![错误提示](https://i.imgur.com/3QzRZbS.png)

---

## 🔍 技术亮点解析

| 特性 | 实现方式 |
|------|----------|
| **网络请求** | 使用 `http.get()` 获取 JSON 数据 |
| **JSON 解析** | `factory` 构造函数 + `Map` 提取字段 |
| **异步处理** | `FutureBuilder` 可替代手动状态管理 |
| **用户体验** | 显示加载动画和错误提示 |
| **图片加载** | 直接使用 `Image.network` 加载图标 |

---

## 🛡️ 安全建议(进阶)

虽然本例直接写入了 API Key,但在生产环境中应:

- 使用环境变量或配置文件
- 通过后端代理请求避免密钥泄露
- 使用 `dotenv` 包管理敏感信息

```yaml
# 添加到 pubspec.yaml
dev_dependencies:
dotenv: ^5.0.2
```

---

## 🚀 扩展功能建议

你可以继续优化这个应用:

✅ 添加定位功能(获取当前位置天气)
✅ 支持多城市收藏列表
✅ 展示未来几天预报(调用 forecast API)
✅ 深色模式切换
✅ 下拉刷新

---

## 📦 打包发布

构建 APK 发布到手机:

```bash
flutter build apk --release
```

生成 IPA(需 macOS):

```bash
flutter build ipa
```

---

## ✅ 总结

通过这个项目,你学会了如何:

- 使用 Flutter 构建真实世界的应用
- 调用外部 RESTful API
- 解析 JSON 并更新 UI
- 处理加载、成功、错误三种状态
- 设计简洁美观的界面

Flutter 让这一切变得简单而高效!

---

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 16:33:05

手机系统预装应用包彻底删除

本文以华为手机 HarmonyOS 为例。 HarmonyOS/EMUI 系统预装应用删除 华为 HarmonyOS 与 EMUI 系统均为基于安卓底层架构的自主定制系统&#xff0c;二者运行逻辑与原生安卓保持一致。卸载系统预装应用需调用系统级操作权限&#xff0c;该权限通常被称为 ROOT。 鉴于华为已关闭…

作者头像 李华
网站建设 2026/9/29 17:15:02

EmotiVoice能否实现多人对话同步生成?技术可行性评估

EmotiVoice能否实现多人对话同步生成&#xff1f;技术可行性评估 在虚拟角色日益“活”起来的今天&#xff0c;用户不再满足于听到一段机械朗读——他们希望看到两个AI角色展开一场有来有往、情绪起伏的真实对话。这种需求催生了一个关键问题&#xff1a;现有的开源语音合成模型…

作者头像 李华
网站建设 2026/9/29 16:27:11

FusionCompute 8.0 实验环境搭建:完整资源获取与部署指南

FusionCompute 8.0 实验环境搭建&#xff1a;完整资源获取与部署指南 【免费下载链接】FusionCompute8.0资源下载指南分享 本仓库提供了一个详细的资源文件&#xff0c;内含百度网盘连接及提取码&#xff0c;以及详细的资源列表&#xff0c;方便您学习和使用FusionCompute 8.0。…

作者头像 李华
网站建设 2026/9/29 12:21:26

AI主播直播间搭建:EmotiVoice语音部分实现

AI主播直播间搭建&#xff1a;EmotiVoice语音部分实现 在AI主播逐渐走入大众视野的今天&#xff0c;观众早已不再满足于“会说话”的数字人。他们希望看到一个有情绪起伏、能共情互动、甚至带有鲜明性格特征的虚拟形象——而这一切的核心&#xff0c;正是声音。 传统文本转语音…

作者头像 李华
网站建设 2026/9/29 11:14:56

静态代码扫描服务 100分(python、java、c++、js、c

静态代码扫描服务 100分&#xff08;python、java、c、js、c&#xff09;题目静态扫描可以快速识别源代码的缺陷&#xff0c;静态扫描的结果以扫描报告作为输出&#xff1a;1、文件扫描的成本和文件大小相关&#xff0c;如果文件大小为N&#xff0c;则扫描成本为N个金币2、扫描…

作者头像 李华
网站建设 2026/9/29 16:42:47

Directus周起始日难题:3步从周日切换到周一的技术解决方案

Directus周起始日难题&#xff1a;3步从周日切换到周一的技术解决方案 【免费下载链接】directus Directus 是一个开源的、实时的内容管理平台&#xff0c;用于构建可扩展的数据管理应用程序。* 管理和操作数据库数据&#xff1b;支持多种数据库类型&#xff1b;支持自定义字段…

作者头像 李华