引言
想要快速构建自己的APP,需要掌握一些基础知识和技能。本篇将项目中常用的三方库做了整理汇总,可以根据自己的需求和项目特点进行选择。无论你是新手还是老手,都可以从这些推荐的三方常用库中受益。
前言
本篇列举的三方库都是小编自己项目里确切使用过的。下面从职责划分的维度,对常用三方库进行整理归纳。
欢迎评论区补充、交流学习。
App 架构
1. 屏幕适配
flutter_screenutil: flutter 屏幕适配方案,用于调整屏幕和字体大小的flutter插件,让你的UI在不同尺寸的屏幕上都能显示合理的布局!
dart
// 用法示例:
// 初始化方式:
ScreenUtilInit(
// 填入设计稿中的尺寸
designSize: const Size(960, 540),
builder: (context, widget) {
return const HomePage();
},
)
// 如何适配尺寸:使用.w、.h 标识宽高即可
Container(
width: 50.w,
height:200.h
)
虽然对代码有侵入性,但确是目前比较常用的适配方案,小编在 Android 、iOS 、windows 等平台中均使用的该库进行适配。
2. 路由
fluro: 组件化开发中,路由是必不可少的成员之一,用于模块间依赖解耦。fluro 是我们早期使用的一个路由库,使用方式简单,因为它本身不支持路由返回 widget,后期我们自己内部封装了一个库替代。
dart
/// Push a route with custom RouteSettings if you don't want to use path params
FluroRouter.appRouter.navigateTo(
context,
'home',
routeSettings: RouteSettings(
arguments: MyArgumentsDataClass('foo!'),
),
);
/// Extract the arguments using [BuildContext.settings.arguments] or [BuildContext.arguments] for short
var homeHandler = Handler(
handlerFunc: (context, params) {
final args = context.settings.arguments as MyArgumentsDataClass;
return HomeComponent(args);
},
);
3. 网络请求
dio: 一个强大的 HTTP 网络请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器、转换器等。
dart
// 用法示例:
final response = await dio.post('/test', data: {'id': 12, 'name': 'dio'});
4. 权限申请
permission_handler: 封装有 Android、iOS 应用权限操作申请,使用方式简洁。
dart
// 用法示例:
await Permission.camera
.onDeniedCallback(() {
// Your code
})
.onGrantedCallback(() {
// Your code
})
.onPermanentlyDeniedCallback(() {
// Your code
})
.onRestrictedCallback(() {
// Your code
})
.onLimitedCallback(() {
// Your code
})
.onProvisionalCallback(() {
// Your code
})
.request();
5. 状态管理
状态管理,它可以轻松地将展示层代码与业务逻辑分开,构建数据驱动的开发模式。推荐自己在用的两个状态管理库:
flutter_bloc: 个人认为链路最简洁的状态管理库,内部实现原理初学者也能看懂。
dart
BlocBuilder<BlocA, BlocAState>(
buildWhen: (previousState, state) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
flutter_riverpod: 官方大力推荐的状态管理库,响应式缓存和数据板顶框架。
dart
// 数据源
@riverpod
Future<String> boredSuggestion(BoredSuggestionRef ref) async {
final response = await http.get(
Uri.https('https://boredapi.com/api/activity'),
);
final json = jsonDecode(response.body);
return json['activity']! as String;
}
// UI层监听数据源变动
class Home extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final boredSuggestion = ref.watch(boredSuggestionProvider);
// Perform a switch-case on the result to handle loading/error states
return boredSuggestion.when(
loading: () => Text('loading'),
error: (error, stackTrace) => Text('error: $error'),
data: (data) => Text(data),
);
}
}
6. 嵌套地狱( widget )
styled_widget: 得益于 Dart2.7.0 后支持 extension 语法,该工具库用于改善 widget 的嵌套地狱问题,提高可阅读性。
dart
// 干掉嵌套后,代码可以这样优雅
Icon(OMIcons.home, color: Colors.white)
.padding(all: 10)
.decorated(color: Color(0xff7AC1E7), shape: BoxShape.circle)
.padding(all: 15)
.decorated(color: Color(0xffE8F2F7), shape: BoxShape.circle)
.padding(all: 20)
.card(
elevation: 10,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
)
.alignment(Alignment.center)
.backgroundColor(Color(0xffEBECF1));
7. 实时崩溃上报
firebase_crashlytics: firebase 全家桶之一,该库用于实时上报崩溃日志,适用于 Android、iOS 平台。
具体集成方式请查阅详情页。
UI 交互
1. 图片加载(缓存)
cached_network_image: 提供缓存能力的图片显示库。
dart
// 用法示例:
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
2. 上拉下拉(刷新)
关于列表的上拉加载更多,下拉刷新,我们在实战中用过两个三方库,分别是:pull_to_refresh和easy_refresh
dart
// 用法示例:
EasyRefresh(
header: Header(
position: IndicatorPosition.locator,
),
footer: Footer(
position: IndicatorPosition.locator,
),
onRefresh: () async {
....
},
onLoad: () async {
....
return IndicatorResult.noMore;
},
child: CustomScrollView(
slivers: [
SliverAppBar(),
const HeaderLocator.sliver(),
...
const FooterLocator.sliver(),
],
),
);
3. 提示(toast)
fluttertoast: 支持 Android、iOS、web 三端。如果要在 Mac、windows 上也实现类型功能,可以使用 overlay 封装一套自己 toast 的功能库。
dart
Fluttertoast.showToast(
msg: "This is Center Short Toast",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
);
4. webview
webview_flutter: flutter 应用内嵌套加载网页,支持 js 与 dart 通信交互。
具体集成方式请查阅详情页。
5. 二维码
qr_code_scanner: 用于扫码识别二维码内容。
dart
// 监听扫码结果
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
// scanData 就是扫码结果
});
}
// UI
QRView (
key: qrKey,
onQRViewCreated: _onQRViewCreated,
)
barcode_widget: 用于将字符串、数字显示为二维码、一维码。
dart
BarcodeWidget(
barcode: Barcode.code128(),
data: 'Hello Flutter',
);
6. 唤起三方应用(URL schema)
url_launcher: 支持 schema url 跳转三方应用。支持全平台。
dart
// 用法示例:
final Uri _url = Uri.parse('https://flutter.cn');
launchUrl(_url); //会触发自动打开浏览器跳转目标页面
// 举例一下打开淘宝
_launchURL() async {
String url ="taobao://item.taobao.com/item.html?id=41700658839";
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
再附上一个网上搜来的 schema集合 :
dart
QQ: mqq://
微信: weixin://
京东: openapp.jdmoble://
淘宝: taobao://
美团: imeituan://
点评: dianping://
1号店: wccbyihaodian://
支付宝: alipay://
微博: sinaweibo://
腾讯微博: TencentWeibo://
weico微博: weico://
知乎: zhihu://
豆瓣fm: doubanradio://
网易公开课: ntesopen://
Chrome: googlechrome://
QQ浏览器: mqqbrowser://
uc浏览器: ucbrowser://
搜狗浏览器: SogouMSE://
百度地图: baidumap:// bdmap://
优酷: youku://
人人: renren://
我查查: wcc://
有道词典: yddictproapp://
微盘: sinavdisk://
名片全能王: camcard://
7. 文本大小自适应
auto_size_text: Flutter小部件,可根据父控件的宽高约束,自动调整文本大小,使其完全适合其边界。
dart
SizeBox(
height: 100,
child: AutoSizeText(
'The text to display',
style: TextStyle(fontSize: 20),
)
)
8. 可见性监听(widget)
visibility_detector: 可监听 widget 可见度(显示的百分比),当百分比为0时,即完全不可见。
dart
@override
Widget build(BuildContext context) {
return VisibilityDetector(
key: Key('my-widget-key'),
onVisibilityChanged: (visibilityInfo) {
var visiblePercentage = visibilityInfo.visibleFraction * 100;
debugPrint(
'Widget ${visibilityInfo.key} is ${visiblePercentage}% visible');
},
child: someOtherWidget,
);
}
9. 图片预览(支持缩放)
photo_view: 支持全平台,含单张、批量两种预览模式。
dart
PhotoView(
imageProvider: AssetImage("assets/large-image.jpg"),
)
数据存储
1. 本地数据库
sqflite: 支持 iOS、Android、Mac,使用 sqlite
语法进行编辑查询。 sqflite_common_ffi: 支持桌面应用,例如 windows 平台。
2. 键值对存储(sp)
shared_preferences: 支持全平台。
dart
// Obtain shared preferences.
final SharedPreferences prefs = await SharedPreferences.getInstance();
// Save an integer value to 'counter' key.
await prefs.setInt('counter', 10);
// Save an boolean value to 'repeat' key.
await prefs.setBool('repeat', true);
// Save an double value to 'decimal' key.
await prefs.setDouble('decimal', 1.5);
// Save an String value to 'action' key.
await prefs.setString('action', 'Start');
// Save an list of strings to 'items' key.
await prefs.setStringList('items', <String>['Earth', 'Moon', 'Sun']);
数据处理
1. 唯一值(uuid)
uuid: 生成 uuid 唯一值。
dart
var uuid = Uuid();
// Generate a v1 (time-based) id
uuid.v1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a'
// Generate a v4 (random) id
uuid.v4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1'
// Generate a v5 (namespace-name-sha1-based) id
uuid.v5(Uuid.NAMESPACE_URL, 'www.google.com'); // -> 'c74a196f-f19d-5ea9-bffd-a2742432fc9c'
2. 精度处理(数值计算)
decimal: 处理数值计算时精度丢失问题。
dart
// with double
print(0.2 + 0.1); // displays 0.30000000000000004
// with decimal
print(Decimal.parse('0.2') + Decimal.parse('0.1')); // displays 0.3
3. 加解密
encrypt: aes 、rsa 加解密处理。
dart
import 'package:encrypt/encrypt.dart';
void main() {
final plainText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit';
final key = Key.fromUtf8('my 32 length key................');
final iv = IV.fromLength(16);
final encrypter = Encrypter(AES(key));
final encrypted = encrypter.encrypt(plainText, iv: iv);
final decrypted = encrypter.decrypt(encrypted, iv: iv);
print(decrypted); // Lorem ipsum dolor sit amet, consectetur adipiscing elit
print(encrypted.base64); // R4PxiU3h8YoIRqVowBXm36ZcCeNeZ4s1OvVBTfFlZRdmohQqOpPQqD1YecJeZMAop/hZ4OxqgC1WtwvX/hP9mw==
}
crypto: sha 、md5 加解密处理。
dart
import 'package:crypto/crypto.dart';
import 'dart:convert'; // for the utf8.encode method
// md5 加密
String generate_MD5(String data) {
var content = new Utf8Encoder().convert(data);
var digest = md5.convert(content);
// 这里其实就是 digest.toString()
return hex.encode(digest.bytes);
}
4. base64
自带库 convert
中提供 base64 转换方法,例如将一个文件流转化为 base64 示例如下:
dart
static Future<String> file2Base64(String path) async {
File file = new File(path);
List<int> bytes = await file.readAsBytes();
return base64Encode(bytes);
}
设备信息
1. 获取ip
r_get_ip: 获取设备当前网络环境分配的ip地址。
dart
RGetIp.internalIP
2. 包信息
package_info_plus: 支持全平台,支持获取应用名称、包名、应用版本号、build版本号等。
dart
import 'package:package_info_plus/package_info_plus.dart';
...
// Be sure to add this line if `PackageInfo.fromPlatform()` is called before runApp()
WidgetsFlutterBinding.ensureInitialized();
...
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
3. 设备信息
device_info_plus: 支持全平台,支持获取相关设备信息,例如 deviceId等。
dart
import 'package:device_info_plus/device_info_plus.dart';
final deviceInfoPlugin = DeviceInfoPlugin();
final deviceInfo = await deviceInfoPlugin.deviceInfo;
final allInfo = deviceInfo.data;
JSON 数据处理
1. 序列化
自带库 convert
中提供 json 序列化、反序列化能力,如下:
dart
// map 转 string
json.encode(mapObj)
// string 转 map
json.decode(string)
2. 模型解析
对应 json 字符串转数据模型,网上用的最多的是 json_serializable,在使用过程中发现其对不存在的空字段解析时会报错,所以我们封装了一套自己的 json 解析库,smart_dart_json (已开源)
dart
import 'package:smart_dart_json/smart_dart_json.dart';
void main() {
const json =
'{"title": "示例title", "data": [{ "name": "rex", "age": 10 }] }';
final sJson = SDartJson(json);
final title = sJson['title'].stringValue;
final students = sJson['data']
.arrayValue
.map(
(e) => Student.fromJson(e), //json 转 模型
)
.toList();
//模拟未定义的字段获取 (方式一)
final undefinedKey1 = sJson['undefinedKey1'].stringValue;
//模拟未定义的字段获取 (方式二)
final undefinedKey2 = sJson['undefinedKey2'].string;
print('title : $title');
print('undefinedKey1 : $undefinedKey1'); // 打印结果为:""
print('undefinedKey2 : $undefinedKey2'); // 打印结果为 null
}
class Student {
final String name;
final int age;
Student({
required this.name,
required this.age,
});
factory Student.fromJson(SDartJson sJson) {
return Student(
name: sJson['name'].stringValue,
age: sJson['age'].intValue,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{}
..['name'] = name
..['age'] = age;
}
}
文件相关
1. PDF预览
advance_pdf_viewer2: PDF 预览,支持 Android、iOS。
dart
// Load from assets
PDFDocument doc = await PDFDocument.fromAsset('assets/test.pdf');
// Load from URL
PDFDocument doc = await PDFDocument.fromURL('http://www.africau.edu/images/default/sample.pdf');
// Load from file
File file = File('...');
PDFDocument doc = await PDFDocument.fromFile(file);
dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Center(
child: _isLoading
? Center(child: CircularProgressIndicator())
: PDFViewer(document: document)),
);
}
2. 保存到相册
image_gallery_saver: 支持 Android、iOS。
dart
// 用法示例
// 下载网络图片
var response = await Dio().get(url, options: Options(responseType: ResponseType.bytes));
// 获取网络图片名称
String name = url.substring(url.lastIndexOf("/") + 1, url.length);
// 保存到相册
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
quality: 60,
name: name,
);
3. 拍照、相册选择
dart
// 拍照
final _picker = ImagePicker();
final imageFile = await _picker.getImage(
source: ImageSource.camera,
maxHeight: 1920,
maxWidth: 1080,
);
dart
// 从相册选择
List<AssetEntity>? assets = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
themeColor: Theme.of(context).primaryColor,
requestType: RequestType.image,
maxAssets: 9,
),
);
4. 文件选择(支持多文件)
dart
// 指定可选文件后缀
final checkedFiles = await FilePicker.platform.pickFiles(
allowMultiple: true,
type: FileType.custom,
allowedExtensions: ['jpg','pdf'],
);
// 不指定文件类型,允许多选
final data = await FilePicker.platform.pickFiles(allowMultiple: true);
5. 获取系统文件路径
path_provider: 一个用于查找文件系统中常用位置的Flutter插件。支持Android, iOS, Linux, macOS和Windows。并非所有平台都支持所有方法。
dart
final Directory tempDir = await getTemporaryDirectory();
final Directory appDocumentsDir = await getApplicationDocumentsDirectory();
final Directory? downloadsDir = await getDownloadsDirectory();
特殊需求
1. 双屏应用(一般用于收银设备)
flutter_subscreen_plugin: 支持双屏安卓设备,主副屏使用Flutter进行绘制,提供主副屏通信交互能力。
dart
// 入口标识主副屏对应的 widget
void main() {
var defaultRouteName = window.defaultRouteName;
if ("subMain" == defaultRouteName) {
viceScreenMain();
} else {
defaultMain();
}
}
//主屏ui
void defaultMain() {
runApp(MainApp());
}
//副屏ui
void viceScreenMain() {
runApp(SubApp());
}
dart
// 主屏发送数据给副屏
SubScreenPlugin.sendMsgToViceScreen("data", params: {"params": "123"});
2. HTML解析
flutter_html: 一个Flutter小部件,用于将HTML和CSS呈现为Flutter小部件。
dart
import 'package:html/parser.dart' as htmlparser;
import 'package:html/dom.dart' as dom;
...
String htmlData = """<div>
<h1>Demo Page</h1>
<p>This is a fantastic product that you should buy!</p>
<h3>Features</h3>
<ul>
<li>It actually works</li>
<li>It exists</li>
<li>It doesn't cost much!</li>
</ul>
<!--You can pretty much put any html in here!-->
</div>""";
dom.Document document = htmlparser.parse(htmlData);
/// sanitize or query document here
Widget html = Html(
document: document,
);
3. Windows常用API
win32: 使用FFI封装了一些最常见的Win32 API调用,使它们可以在不需要C编译器或Windows SDK的情况下被Dart代码访问。
功能提供列表可查看:链接
4. 文字播报
dart
await flutterTts.speak("Hello World");
5. 播放本地音频
dart
import 'package:just_audio/just_audio.dart';
final player = AudioPlayer(); // Create a player
final duration = await player.setUrl( // Load a URL
'https://foo.com/bar.mp3'); // Schemes: (https: | file: | asset: )
player.play(); // Play without waiting for completion
await player.play(); // Play while waiting for completion
await player.pause(); // Pause but remain ready to play
await player.seek(Duration(second: 10)); // Jump to the 10 second position
await player.setSpeed(2.0); // Twice as fast
await player.setVolume(0.5); // Half as loud
await player.stop(); // Stop and free resources
6. ijk视频播放器
dart
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/material.dart';
class VideoScreen extends StatefulWidget {
final String url;
VideoScreen({@required this.url});
@override
_VideoScreenState createState() => _VideoScreenState();
}
class _VideoScreenState extends State<VideoScreen> {
final FijkPlayer player = FijkPlayer();
_VideoScreenState();
@override
void initState() {
super.initState();
player.setDataSource(widget.url, autoPlay: true);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Fijkplayer Example")),
body: Container(
alignment: Alignment.center,
child: FijkView(
player: player,
),
));
}
@override
void dispose() {
super.dispose();
player.release();
}
}
7. 连接电子秤
flutter_weigh_serial: 提供获取电子秤数据能力,支持 Android、Windows 平台。注意:只支持串口方式连接(包括usb转串口)的称重设备。
dart
import 'package:flutter_weigh_serial/flutter_weigh_serial.dart';
class _MyAppState extends State<MyApp> {
late WeighSerialProvider weighSerialProvider;
@override
void initState() {
super.initState();
weighSerialProvider = WeighSerialProvider();
}
@override
void dispose() {
weighSerialProvider.close(); //退出页面时断开连接
super.dispose();
}
...
// 连接称重设备的方法如下:
void _connectWeigh() {
weighSerialProvider.findAndConnect().then(
(success) {
if (success) {
//搜索到称重设备并连接成功
weighSerialProvider.weighListener?.listen(
(data) {
// 获取到称重数据,返回数据模型 *WeighResult*
log('称重数据 - ${data.toMap().toString()}');
},
);
} else {
Fluttertoast.showToast(msg: '称重设备连接失败');
}
},
onError: (e) {
Fluttertoast.showToast(msg: '称重设备连接失败(${e.toString()})');
},
);
}
}
8. 小票标签打印
flutter_printer_plus: flutter 端 【小票、标签】打印能力实现,支持 USB、网口,支持 Android、Windows 平台。
使用 widget 开发票据样式,直接将 flutter widget 转图像数据进行打印,支持传输方式:usb连接(支持Android、Windows)、网络连接(各平台通用)。
使用方式可点击查阅:example
9. 扫码监听
scan_gun: 实现扫码枪获取数据源,禁止系统键盘弹窗(不会触发键盘唤起,不会触发中文乱码)
dart
// 提供 `ScanMonitorWidget` 作为父节点,嵌套使用:
ScanMonitorWidget({
Key? key,
required ChildBuilder childBuilder, //自己的 widget
FocusNode? scanNode,
FocusNode? textFiledNode,
required void Function(String) onSubmit, //监听到的扫码枪内容
})
10. 桌面应用窗口大小可调整
window_manager: 这个插件允许Flutter桌面应用程序调整大小和重新定位窗口。提供监听键盘快捷键组合、功能键点击事件能力。
dart
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Must add this line.
await windowManager.ensureInitialized();
WindowOptions windowOptions = WindowOptions(
size: Size(800, 600),
center: true,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden,
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
runApp(MyApp());
}
11. 地图定位
amap_flutter_location: 高德定位Flutter插件。
使用方式可点击预览:链接
12. 手写签名
signature: 一个Flutter插件,提供性能优化的签名画布,能够设置自定义样式,边界和初始状态。支持所有平台。
dart
// IMPORT PACKAGE
import 'package:signature/signature.dart';
// Initialise a controller. It will contains signature points, stroke width and pen color.
// It will allow you to interact with the widget
final SignatureController _controller = SignatureController(
penStrokeWidth: 5,
penColor: Colors.red,
exportBackgroundColor: Colors.blue,
);
// INITIALIZE. RESULT IS A WIDGET, SO IT CAN BE DIRECTLY USED IN BUILD METHOD
var _signatureCanvas = Signature(
controller: _controller,
width: 300,
height: 300,
backgroundColor: Colors.lightBlueAccent,
);
// CLEAR CANVAS
_controller.clear();
// EXPORT BYTES AS PNG
// The exported image will be limited to the drawn area
_controller.toPngBytes();
// isEmpty/isNotEmpty CAN BE USED TO CHECK IF SIGNATURE HAS BEEN PROVIDED
_controller.isNotEmpty; //true if signature has been provided
_controller.isEmpty; //true if signature has NOT been provided
// EXPORT POINTS (2D POINTS ROUGHLY REPRESENTING WHAT IS VISIBLE ON CANVAS)
var exportedPoints = _controller.points;
//EXPORTED POINTS CAN BE USED TO INITIALIZE PREVIOUS CONTROLLER
final SignatureController _controller = SignatureController(points: exportedPoints);
//DONT FORGET TO DISPOSE IT IN THE `dispose()` METHOD OF STATEFUL WIDGETS
@override
void dispose() {
_controller.dispose();
super.dispose();
}