桂林市网站建设_网站建设公司_博客网站_seo优化
2025/12/18 2:01:24 网站建设 项目流程

## 🌤️ 引言

在本篇文章中,我们将使用 **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 让这一切变得简单而高效!

---

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询