第17章 - 常用第三方包
嗨,朋友!我是长安。
Flutter 的生态系统非常丰富,有大量优秀的第三方包可以帮助我们快速开发。这一章,我会介绍在实际项目中最常用的包,让你少走弯路,直接用上最好的工具!
📦 包管理基础
如何添加依赖
在 pubspec.yaml 中添加:
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0 # 网络请求
provider: ^6.1.1 # 状态管理
# 更多包...
然后运行:
flutter pub get
在哪里找包?
- pub.dev - Flutter 官方包仓库
- 搜索时注意看:
- Likes - 点赞数越多越好
- Pub Points - 质量评分(满分 160)
- Popularity - 使用率
- 最近更新时间 - 确保还在维护
🌐 网络请求
dio(强烈推荐)
最流行的 HTTP 客户端,功能强大。
dependencies:
dio: ^5.4.0
import 'package:dio/dio.dart';
final dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
),
);
// GET 请求
final response = await dio.get('/users');
// POST 请求
final response = await dio.post('/users', data: {
'name': '长安',
'email': 'changan@example.com',
});
// 添加拦截器(统一处理 Token、日志等)
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = 'Bearer $token';
return handler.next(options);
},
onError: (error, handler) {
if (error.response?.statusCode == 401) {
// 处理登录过期
}
return handler.next(error);
},
));
retrofit(配合 dio 使用)
声明式 API 定义,类似 Android 的 Retrofit。
dependencies:
retrofit: ^4.1.0
dio: ^5.4.0
dev_dependencies:
retrofit_generator: ^8.1.0
build_runner: ^2.4.8
json_serializable: ^6.7.1
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';
part 'api_client.g.dart';
(baseUrl: 'https://api.example.com')
abstract class ApiClient {
factory ApiClient(Dio dio) = _ApiClient;
('/users')
Future<List<User>> getUsers();
('/users/{id}')
Future<User> getUser(('id') int id);
('/users')
Future<User> createUser(() User user);
}
// 使用
final client = ApiClient(Dio());
final users = await client.getUsers();
运行生成代码:
flutter pub run build_runner build
🖼️ 图片处理
cached_network_image(网络图片缓存)
自动缓存网络图片,避免重复下载。
dependencies:
cached_network_image: ^3.3.1
import 'package:cached_network_image/cached_network_image.dart';
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.error),
fit: BoxFit.cover,
width: 200,
height: 200,
)
// 圆形头像
CircleAvatar(
radius: 40,
backgroundImage: CachedNetworkImageProvider(
'https://example.com/avatar.jpg',
),
)
image_picker(选择图片/拍照)
从相册选择图片或调用相机拍照。
dependencies:
image_picker: ^1.0.7
import 'package:image_picker/image_picker.dart';
final picker = ImagePicker();
// 从相册选择
final XFile? image = await picker.pickImage(
source: ImageSource.gallery,
maxWidth: 1000,
maxHeight: 1000,
imageQuality: 80,
);
// 拍照
final XFile? photo = await picker.pickImage(
source: ImageSource.camera,
);
// 选择多张图片
final List<XFile> images = await picker.pickMultiImage();
// 使用选择的图片
if (image != null) {
File file = File(image.path);
// 上传或显示图片
}
权限配置
Android: 在 android/app/src/main/AndroidManifest.xml 添加:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
iOS: 在 ios/Runner/Info.plist 添加:
<key>NSCameraUsageDescription</key>
<string>需要访问相机来拍照</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册来选择图片</string>
image_cropper(图片裁剪)
裁剪图片,常用于头像上传。
dependencies:
image_cropper: ^5.0.1
import 'package:image_cropper/image_cropper.dart';
final croppedFile = await ImageCropper().cropImage(
sourcePath: imageFile.path,
aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1),
uiSettings: [
AndroidUiSettings(
toolbarTitle: '裁剪图片',
toolbarColor: Colors.blue,
toolbarWidgetColor: Colors.white,
lockAspectRatio: true,
),
IOSUiSettings(
title: '裁剪图片',
aspectRatioLockEnabled: true,
),
],
);
if (croppedFile != null) {
// 使用裁剪后的图片
File file = File(croppedFile.path);
}
📱 UI 组件
flutter_screenutil(屏幕适配)
让 UI 在不同屏幕尺寸上保持一致。
dependencies:
flutter_screenutil: ^5.9.0
import 'package:flutter_screenutil/flutter_screenutil.dart';
// 在 main.dart 初始化
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 812), // 设计稿尺寸
minTextAdapt: true,
builder: (context, child) {
return MaterialApp(
home: HomePage(),
);
},
);
}
}
// 使用
Container(
width: 100.w, // 宽度适配
height: 50.h, // 高度适配
padding: EdgeInsets.all(16.r), // 圆角/间距适配
child: Text(
'你好',
style: TextStyle(fontSize: 16.sp), // 字体适配
),
)
shimmer(骨架屏/闪光加载效果)
优雅的加载占位效果。
dependencies:
shimmer: ^3.0.0
import 'package:shimmer/shimmer.dart';
// 基本用法
Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
width: 200,
height: 100,
color: Colors.white,
),
)
// 列表骨架屏
ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
// 头像占位
Container(
width: 50,
height: 50,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
),
const SizedBox(width: 16),
// 文字占位
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
height: 16,
color: Colors.white,
),
const SizedBox(height: 8),
Container(
width: 100,
height: 14,
color: Colors.white,
),
],
),
),
],
),
),
);
},
)
flutter_spinkit(加载动画)
多种漂亮的加载动画。
dependencies:
flutter_spinkit: ^5.2.0
import 'package:flutter_spinkit/flutter_spinkit.dart';
// 各种加载动画
SpinKitWave(color: Colors.blue, size: 50)
SpinKitCircle(color: Colors.blue, size: 50)
SpinKitCubeGrid(color: Colors.blue, size: 50)
SpinKitPulse(color: Colors.blue, size: 50)
SpinKitFadingCircle(color: Colors.blue, size: 50)
SpinKitDoubleBounce(color: Colors.blue, size: 50)
SpinKitThreeBounce(color: Colors.blue, size: 50)
pull_to_refresh(下拉刷新/上拉加载)
强大的下拉刷新和上拉加载组件。
dependencies:
pull_to_refresh: ^2.0.0
import 'package:pull_to_refresh/pull_to_refresh.dart';
class MyListPage extends StatefulWidget {
State<MyListPage> createState() => _MyListPageState();
}
class _MyListPageState extends State<MyListPage> {
final RefreshController _refreshController = RefreshController();
List<String> items = [];
int page = 1;
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
// 加载数据...
final newItems = await fetchItems(page);
setState(() {
items = newItems;
});
}
Future<void> _onRefresh() async {
page = 1;
final newItems = await fetchItems(page);
setState(() {
items = newItems;
});
_refreshController.refreshCompleted();
}
Future<void> _onLoading() async {
page++;
final newItems = await fetchItems(page);
if (newItems.isEmpty) {
_refreshController.loadNoData();
} else {
setState(() {
items.addAll(newItems);
});
_refreshController.loadComplete();
}
}
Widget build(BuildContext context) {
return SmartRefresher(
controller: _refreshController,
enablePullDown: true,
enablePullUp: true,
header: const WaterDropHeader(),
footer: const ClassicFooter(
loadingText: '加载中...',
noDataText: '没有更多了',
idleText: '上拉加载更多',
),
onRefresh: _onRefresh,
onLoading: _onLoading,
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(
title: Text(items[index]),
),
),
);
}
void dispose() {
_refreshController.dispose();
super.dispose();
}
}
🎯 状态管理
provider(官方推荐)
Flutter 官方推荐的状态管理方案。
dependencies:
provider: ^6.1.1
import 'package:provider/provider.dart';
// 1. 创建状态类
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 2. 在顶层提供状态
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterProvider(),
child: const MyApp(),
),
);
}
// 3. 在 Widget 中使用
class CounterPage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: Center(
// 读取状态
child: Consumer<CounterProvider>(
builder: (context, counter, child) {
return Text('Count: ${counter.count}');
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 修改状态
context.read<CounterProvider>().increment();
},
child: const Icon(Icons.add),
),
);
}
}
get(GetX)
功能最全面的包:状态管理 + 路由 + 依赖注入 + 国际化。
dependencies:
get: ^4.6.6
import 'package:get/get.dart';
// 1. 创建 Controller
class CounterController extends GetxController {
var count = 0.obs; // .obs 让变量变成响应式
void increment() => count++;
}
// 2. 使用 GetMaterialApp
void main() {
runApp(GetMaterialApp(home: HomePage()));
}
// 3. 在页面中使用
class HomePage extends StatelessWidget {
// 注入 Controller
final controller = Get.put(CounterController());
Widget build(BuildContext context) {
return Scaffold(
body: Center(
// Obx 自动监听响应式变量
child: Obx(() => Text('Count: ${controller.count}')),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
child: const Icon(Icons.add),
),
);
}
}
// GetX 路由(无需 context)
Get.to(() => DetailPage()); // 跳转
Get.toNamed('/detail'); // 命名路由
Get.back(); // 返回
Get.off(() => LoginPage()); // 跳转并移除当前页
Get.offAll(() => HomePage()); // 清空所有路由
// GetX SnackBar(无需 context)
Get.snackbar('标题', '内容');
// GetX Dialog
Get.defaultDialog(
title: '提示',
middleText: '确定要删除吗?',
confirm: TextButton(
onPressed: () => Get.back(),
child: const Text('确定'),
),
);
riverpod(Provider 升级版)
更强大、更灵活的状态管理。
dependencies:
flutter_riverpod: ^2.4.10
import 'package:flutter_riverpod/flutter_riverpod.dart';
// 1. 创建 Provider
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
}
// 2. 使用 ProviderScope 包裹应用
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
// 3. 在 Widget 中使用(继承 ConsumerWidget)
class CounterPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Text('Count: $count'),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
🧭 路由管理
go_router(官方推荐)
Flutter 官方推荐的声明式路由。
dependencies:
go_router: ^13.2.0
import 'package:go_router/go_router.dart';
// 1. 定义路由
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/detail/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailPage(id: id);
},
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsPage(),
),
],
errorBuilder: (context, state) => const NotFoundPage(),
);
// 2. 使用 GoRouter
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}
// 3. 导航
context.go('/detail/123'); // 跳转
context.push('/detail/123'); // 入栈
context.pop(); // 返回
context.goNamed('detail', pathParameters: {'id': '123'});
💾 本地存储
shared_preferences(轻量存储)
存储简单的键值对数据。
dependencies:
shared_preferences: ^2.2.2
import 'package:shared_preferences/shared_preferences.dart';
// 保存数据
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', '长安');
await prefs.setInt('age', 25);
await prefs.setBool('isVip', true);
await prefs.setStringList('tags', ['Flutter', 'Dart']);
// 读取数据
final username = prefs.getString('username') ?? '';
final age = prefs.getInt('age') ?? 0;
final isVip = prefs.getBool('isVip') ?? false;
final tags = prefs.getStringList('tags') ?? [];
// 删除数据
await prefs.remove('username');
// 清空所有
await prefs.clear();
hive(高性能 NoSQL 数据库)
轻量级、高性能的本地数据库。
dependencies:
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.4.8
import 'package:hive_flutter/hive_flutter.dart';
// 1. 初始化
void main() async {
await Hive.initFlutter();
runApp(const MyApp());
}
// 2. 基本使用
final box = await Hive.openBox('settings');
// 存储
await box.put('theme', 'dark');
await box.put('fontSize', 16);
// 读取
final theme = box.get('theme', defaultValue: 'light');
// 删除
await box.delete('theme');
// 3. 存储对象(需要注册适配器)
(typeId: 0)
class User extends HiveObject {
(0)
late String name;
(1)
late int age;
}
// 注册适配器(运行 build_runner 生成)
Hive.registerAdapter(UserAdapter());
// 存储对象
final userBox = await Hive.openBox<User>('users');
final user = User()
..name = '长安'
..age = 25;
await userBox.add(user);
sqflite(SQLite 数据库)
传统关系型数据库,适合复杂查询。
dependencies:
sqflite: ^2.3.2
path: ^1.8.3
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
// 数据库帮助类
class DatabaseHelper {
static Database? _database;
Future<Database> get database async {
_database ??= await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
final path = join(await getDatabasesPath(), 'app.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT
)
''');
},
);
}
// 插入
Future<int> insertUser(Map<String, dynamic> user) async {
final db = await database;
return await db.insert('users', user);
}
// 查询
Future<List<Map<String, dynamic>>> getUsers() async {
final db = await database;
return await db.query('users');
}
// 更新
Future<int> updateUser(int id, Map<String, dynamic> user) async {
final db = await database;
return await db.update('users', user, where: 'id = ?', whereArgs: [id]);
}
// 删除
Future<int> deleteUser(int id) async {
final db = await database;
return await db.delete('users', where: 'id = ?', whereArgs: [id]);
}
}
🛠️ 工具类
intl(日期格式化/国际化)
日期时间格式化和国际化支持。
dependencies:
intl: ^0.19.0
import 'package:intl/intl.dart';
final now = DateTime.now();
// 日期格式化
DateFormat('yyyy-MM-dd').format(now); // 2024-01-15
DateFormat('yyyy年MM月dd日').format(now); // 2024年01月15日
DateFormat('MM/dd/yyyy').format(now); // 01/15/2024
DateFormat('HH:mm:ss').format(now); // 14:30:25
DateFormat('yyyy-MM-dd HH:mm').format(now); // 2024-01-15 14:30
// 本地化日期
DateFormat.yMMMMd('zh_CN').format(now); // 2024年1月15日
DateFormat.EEEE('zh_CN').format(now); // 星期一
// 数字格式化
NumberFormat('#,###').format(1234567); // 1,234,567
NumberFormat.currency(locale: 'zh_CN', symbol: '¥').format(99.9); // ¥99.90
NumberFormat.percentPattern().format(0.25); // 25%
url_launcher(打开链接)
打开网页、拨打电话、发送邮件等。
dependencies:
url_launcher: ^6.2.4
import 'package:url_launcher/url_launcher.dart';
// 打开网页
await launchUrl(Uri.parse('https://flutter.dev'));
// 在浏览器中打开
await launchUrl(
Uri.parse('https://flutter.dev'),
mode: LaunchMode.externalApplication,
);
// 拨打电话
await launchUrl(Uri.parse('tel:+8613800138000'));
// 发送短信
await launchUrl(Uri.parse('sms:+8613800138000'));
// 发送邮件
await launchUrl(Uri.parse('mailto:test@example.com?subject=Hello&body=Hi'));
// 打开地图
await launchUrl(Uri.parse('geo:39.9042,116.4074'));
// 检查是否可以打开
if (await canLaunchUrl(Uri.parse('https://flutter.dev'))) {
await launchUrl(Uri.parse('https://flutter.dev'));
}
share_plus(分享功能)
调用系统分享功能。
dependencies:
share_plus: ^7.2.2
import 'package:share_plus/share_plus.dart';
// 分享文本
await Share.share('查看这个链接:https://flutter.dev');
// 分享文本并指定主题
await Share.share(
'这是一段分享内容',
subject: '分享主题',
);
// 分享文件
await Share.shareXFiles(
[XFile('/path/to/image.jpg')],
text: '看看这张图片!',
);
// 分享多个文件
await Share.shareXFiles([
XFile('/path/to/image1.jpg'),
XFile('/path/to/image2.jpg'),
]);
permission_handler(权限管理)
统一的权限请求和检查。
dependencies:
permission_handler: ^11.3.0
import 'package:permission_handler/permission_handler.dart';
// 检查权限
final status = await Permission.camera.status;
if (status.isGranted) {
// 已授权
} else if (status.isDenied) {
// 被拒绝,可以再次请求
} else if (status.isPermanentlyDenied) {
// 永久拒绝,需要去设置页面开启
await openAppSettings();
}
// 请求权限
final result = await Permission.camera.request();
// 请求多个权限
Map<Permission, PermissionStatus> statuses = await [
Permission.camera,
Permission.photos,
Permission.location,
].request();
// 封装权限请求
Future<bool> requestCameraPermission() async {
var status = await Permission.camera.status;
if (status.isGranted) return true;
status = await Permission.camera.request();
if (status.isGranted) return true;
if (status.isPermanentlyDenied) {
// 提示用户去设置页面开启
final shouldOpen = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('需要相机权限'),
content: const Text('请在设置中开启相机权限'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('去设置'),
),
],
),
);
if (shouldOpen == true) {
await openAppSettings();
}
}
return false;
}
📋 推荐的 pubspec.yaml 模板
以下是一个实际项目常用的依赖配置:
name: my_app
description: A Flutter application.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# UI
cupertino_icons: ^1.0.6
flutter_screenutil: ^5.9.0
shimmer: ^3.0.0
flutter_spinkit: ^5.2.0
cached_network_image: ^3.3.1
# 网络
dio: ^5.4.0
# 状态管理(选择其一)
provider: ^6.1.1
# get: ^4.6.6
# 路由
go_router: ^13.2.0
# 存储
shared_preferences: ^2.2.2
hive_flutter: ^1.1.0
# 工具
intl: ^0.19.0
url_launcher: ^6.2.4
share_plus: ^7.2.2
permission_handler: ^11.3.0
image_picker: ^1.0.7
# 下拉刷新
pull_to_refresh: ^2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
build_runner: ^2.4.8
hive_generator: ^2.0.1
flutter:
uses-material-design: true
📝 小结
这一章我们介绍了实际项目中最常用的第三方包:
| 分类 | 推荐包 | 用途 |
|---|---|---|
| 网络请求 | dio | HTTP 客户端 |
| 图片处理 | cached_network_image, image_picker | 图片缓存、选择 |
| UI 组件 | flutter_screenutil, shimmer | 屏幕适配、骨架屏 |
| 状态管理 | provider / get | 状态管理 |
| 路由 | go_router | 声明式路由 |
| 存储 | shared_preferences, hive | 本地存储 |
| 工具 | intl, url_launcher, permission_handler | 日期格式化、链接、权限 |
选包建议
- 优先用官方包 - 如
http、provider、go_router - 看维护状态 - 最近更新时间超过 6 个月要谨慎
- 看 Pub Points - 140 分以上的包质量较好
- 看 Issues - 活跃的 Issues 说明有人在维护
- 不要过度依赖 - 能自己实现的简单功能就不要引入包
💪 练习题
- 使用
dio封装一个 API 服务类,包含拦截器 - 使用
image_picker实现选择图片并显示 - 使用
shared_preferences实现简单的设置页面
🚀 下一步
了解了常用的第三方包后,你可以前往 附录A - UI 框架与组件库推荐,学习如何快速构建漂亮的界面!
由 编程指南 提供
