news 2026/7/7 20:02:56

# 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/7/7 12:05:57

EmotiVoice团队访谈:未来将增加更多情感类型支持

EmotiVoice团队访谈&#xff1a;未来将增加更多情感类型支持 在虚拟助手越来越“懂人心”、AI主播开始拥有情绪起伏的今天&#xff0c;语音合成技术早已不再满足于“把字读出来”。用户期待的是能传递喜怒哀乐的声音——一句话里藏着紧张的喘息&#xff0c;一段旁白中流露淡淡的…

作者头像 李华
网站建设 2026/7/7 10:05:58

EmotiVoice自动扩缩容方案设计(Kubernetes)

EmotiVoice 自动扩缩容方案设计&#xff08;Kubernetes&#xff09; 在智能语音应用日益普及的今天&#xff0c;文本转语音&#xff08;TTS&#xff09;系统正从“能说”迈向“说得像人”。EmotiVoice 作为一款开源、高表现力的语音合成引擎&#xff0c;凭借其零样本声音克隆与…

作者头像 李华
网站建设 2026/7/7 18:49:22

电子书变有声书:EmotiVoice全自动转换方案

电子书变有声书&#xff1a;EmotiVoice全自动转换方案 在数字阅读愈发普及的今天&#xff0c;越来越多用户开始“听”书而非“读”书。通勤路上、健身途中、睡前放松——碎片化时间催生了对高质量有声内容的巨大需求。然而&#xff0c;传统有声书依赖真人配音&#xff0c;制作周…

作者头像 李华
网站建设 2026/7/7 18:51:28

终极指南:高性能UI交互功能从零实现

如何优化鼠标事件响应速度和跨平台适配&#xff1f;在现代应用开发中&#xff0c;流畅的UI交互体验已成为用户评价产品的重要标准。本文将带你深入探索高性能UI库中的交互功能实现&#xff0c;从事件捕获到渲染优化&#xff0c;全方位解析如何打造响应迅速的跨平台用户界面。 【…

作者头像 李华
网站建设 2026/7/7 7:31:02

flutter setState(() { … }) 作用

在 Flutter 中&#xff0c;setState() 是一个核心方法&#xff0c;用于通知框架某个 State 对象的内部状态已发生变化&#xff0c;从而触发界面的重新构建&#xff08;rebuild&#xff09;。 &#x1f9e0; 核心作用一句话总结&#xff1a; 当你修改了 StatefulWidget 的状态&a…

作者头像 李华
网站建设 2026/7/7 11:49:04

Faiss向量检索精度优化实战:从参数调优到架构创新的完整指南

Faiss向量检索精度优化实战&#xff1a;从参数调优到架构创新的完整指南 【免费下载链接】faiss A library for efficient similarity search and clustering of dense vectors. 项目地址: https://gitcode.com/GitHub_Trending/fa/faiss 你是否曾在深夜调试时发现&…

作者头像 李华