news 2026/4/23 18:36:10

# 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/4/23 12:25:11

EmotiVoice情感分类模型训练过程全公开

EmotiVoice情感分类模型训练过程全公开 在虚拟助手开始对你“冷笑”、游戏NPC因剧情转折而声音颤抖的今天&#xff0c;语音合成早已不再是简单的文字朗读。人们期待的不再是一段清晰但冰冷的语音输出&#xff0c;而是一个能感知情绪、表达情感、甚至带有“人格”的声音伙伴。正…

作者头像 李华
网站建设 2026/4/23 12:25:14

2025全新方案:5步构建高性能现代化Web架构实战指南

2025全新方案&#xff1a;5步构建高性能现代化Web架构实战指南 【免费下载链接】strapi &#x1f680; Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable and developer-first. 项目地址: https://gitcode.com/GitHub…

作者头像 李华
网站建设 2026/4/23 12:25:33

10分钟掌握Lime编辑器:从零到精通的完整指南

还在为寻找完美的开源代码编辑器而纠结吗&#xff1f;作为Sublime Text的完美开源替代品&#xff0c;Lime编辑器凭借其API兼容性和强大扩展性&#xff0c;正在成为开发者们的新宠。无论你是编程新手还是资深开发者&#xff0c;这篇指南都能让你在10分钟内彻底掌握Lime编辑器&am…

作者头像 李华
网站建设 2026/4/23 12:24:21

高校科研单位使用EmotiVoice可申请优惠

高校科研单位使用 EmotiVoice 可申请优惠 在心理学实验室里&#xff0c;研究人员正试图验证一个假设&#xff1a;不同情绪语调的安慰话语&#xff0c;是否会影响受试者的共情反应。过去&#xff0c;他们需要招募多名配音演员&#xff0c;在录音棚中反复录制同一句话的不同版本—…

作者头像 李华
网站建设 2026/4/23 12:25:00

影视剧使用AI语音需注意的法律问题

影视剧使用AI语音需注意的法律问题 在一部即将上线的网络剧中&#xff0c;主角的独白情感充沛、语调起伏自然&#xff0c;观众几乎无法察觉这段声音并非出自真人配音演员之口——它是由AI生成的。这样的场景正变得越来越常见。随着深度学习技术的进步&#xff0c;文本转语音&am…

作者头像 李华
网站建设 2026/4/23 12:17:07

静态站点新革命:Strapi无头CMS实战全解析

静态站点新革命&#xff1a;Strapi无头CMS实战全解析 【免费下载链接】strapi &#x1f680; Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable and developer-first. 项目地址: https://gitcode.com/GitHub_Trending…

作者头像 李华