package_info 获取应用相关信息(版本号、应用名、包名、构建版本号等)
url_launcher 跳转到其他应用/浏览器
flutter_easyrefresh 上下拉刷新
refresh_indicator 上下拉刷新
logging 日志打印
shared_preferences 持久化键值对
sqflite 持久化数据库
marquee 滚动文字组件
image_picker 和 multi_image_pikcer 图片选择器
carousel_slider
cached_network_image 网络图片 加载缓存
flutter_cache_manager
video_player
path_provider 文件管理
cookie_jar
catcher 全局异常捕获
flutter_swiper 轮播图
FocusedMenuHolder 长按
flutter_easyloading 防重提交时的loading蒙版
socket_io_client(WebSocket插件)
fl_chart 图表插件(线条图、柱状图、散点图、饼图)
functional_widget 自动生成类组件
motion_toast 弹框
package_info 获取应用相关信息(版本号、应用名、包名、构建版本号等)
import 'package:package_info/package_info.dart';
PackageInfo.fromPlatform().then((packageInfo){
});
注意:
1. 如果没有给设置iOS的displayName会导致获取不到appName,运行会提示出错。
url_launcher 跳转到其他应用/浏览器
import 'package:url_launcher/url_launcher.dart';
const _url = 'https://flutter.cn';
void _launchURL() async => await canLaunch(_url) ? await launch(_url) : throw '不能打开 $_url';
flutter_easyrefresh 上下拉刷新
import 'package:flutter_easyrefresh/easy_refresh.dart';
EasyRefresh(
child: ScrollView(),
onRefresh: () async{
},
onLoad: () async {
},
)
首次加载时并不会自动加载,需要使用 EasyRefreshController 来控制。
支持自定义header和footer
refresh_indicator 上下拉刷新
logging 日志打印
shared_preferences 持久化键值对
类似于iOS的NSUserDefaults、Android的SharedPreferences
可用来存储:布尔值、整型、浮点型、字符串、字符串数组。对象需要做json序列化后才能存储。
import 'package:shared_preferences/shared_preferences.dart';
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
await prefs.setInt('counter', counter);
}
sqflite 持久化数据库
// Get a location using getDatabasesPath
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'demo.db');
// Delete the database
await deleteDatabase(path);
// open the database
Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
// When creating the db, create the table
await db.execute(
'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)');
});
// Insert some records in a transaction
await database.transaction((txn) async {
int id1 = await txn.rawInsert(
'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');
print('inserted1: $id1');
int id2 = await txn.rawInsert(
'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)',
['another name', 12345678, 3.1416]);
print('inserted2: $id2');
});
// Update some record
int count = await database.rawUpdate(
'UPDATE Test SET name = ?, value = ? WHERE name = ?',
['updated name', '9876', 'some name']);
print('updated: $count');
// Get the records
List<Map> list = await database.rawQuery('SELECT * FROM Test');
List<Map> expectedList = [
{'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789},
{'name': 'another name', 'id': 2, 'value': 12345678, 'num': 3.1416}
];
print(list);
print(expectedList);
assert(const DeepCollectionEquality().equals(list, expectedList));
// Count the records
count = Sqflite
.firstIntValue(await database.rawQuery('SELECT COUNT(*) FROM Test'));
assert(count == 2);
// Delete a record
count = await database
.rawDelete('DELETE FROM Test WHERE name = ?', ['another name']);
assert(count == 1);
// Close the database
await database.close();
marquee 滚动文字组件
支持设置文字的多种参数(如字体,滚动方向,留白,滚动速度)
Marquee(
text: 'Some sample text that takes some space.',
style: TextStyle(fontWeight: FontWeight.bold),
scrollAxis: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.start,
blankSpace: 20.0,
velocity: 100.0,
pauseAfterRound: Duration(seconds: 1),
startPadding: 10.0,
accelerationDuration: Duration(seconds: 1),
accelerationCurve: Curves.linear,
decelerationDuration: Duration(milliseconds: 500),
decelerationCurve: Curves.easeOut,
)
image_picker 和 multi_image_pikcer 图片选择器
前者支持单张图片选择,后者支持多图选择,二者均支持相机或从相册选择图片。需要注意的是 multi_image_picker 默认语言是英文的,需要自己配置本地语言。
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
class _MyHomePageState extends State<MyHomePage> {
File _image;
final picker = ImagePicker();
Future getImage() async {
final pickedFile = await picker.getImage(source: ImageSource.camera);
setState(() {
if (pickedFile != null) {
_image = File(pickedFile.path);
} else {
print('No image selected.');
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Image Picker Example'),
),
body: Center(
child: _image == null
? Text('No image selected.')
: Image.file(_image),
),
floatingActionButton: FloatingActionButton(
onPressed: getImage,
tooltip: 'Pick Image',
child: Icon(Icons.add_a_photo),
),
);
}
}
carousel_slider 轮播图
cached_network_image 网络图片 加载缓存
CachedNetworkImage(
imageUrl: "http://dd.com/350x150",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
flutter_cache_manager
video_player
path_provider 文件管理
getApplicationDocumentsDirectory
应用文档目录(不会被系统清除,用户数据存储目录),对于安卓推荐使用外部存储getExternalStorageDirectory。
getLibraryDirectory
指向应用可以持久存储数据的目录,不支持安卓平台。
getTemporaryDirectory
应用临时目录(可能被清除)
getApplicationSupportDirectory
应用支持目录,一般放置与用户无关的数据。
getExternalStorageDirectory
获取外部存储目录,不支持 iOS 平台。
getExternalCacheDirectories
获取外部缓存目录,,不支持 iOS 平台。
getExternalStorageDirectories
获取外部的目录列表,不支持 iOS 平台。
getDownloadsDirectory
获取下载目录,用于 Web 端,不支持安卓和 iOS平台。
例
getTemporaryDirectory().then((tempDir) => {_destPath = tempDir.path + 'googlechrome.dmg'});
1. OS Error: Read-only file system
安卓系统需要获取READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限。
2. onReceivedProgress中total=-1
表示该文件被压缩或者需要会话信息才可以下载(如后端开启了验证)。
3. 删除文件的时候需要检查文件是否在下载过程中,如果在下载过程中删除会引起文件读写冲突,抛出异常。
4. CancelToken一个实例只能取消一次请求
因此每次发起请求的时候需要重新构建CancelToken对象,否则取消一次后无法再次取消。
cookie_jar
catcher 全局异常捕获
import 'package:catcher/catcher.dart';
main() {
// 1.
// 创建debugCatcher,错误发生后会弹框,并输出到控制台
CatcherOptions debugOptions =
CatcherOptions(DialogReportMode(), [ConsoleHandler()]);
// 创建releaseCatcher,错误发生后会弹框,并将错误发送到邮箱
CatcherOptions releaseOptions = CatcherOptions(DialogReportMode(), [
EmailManualHandler(["support@email.com"])
]);
// 2.
Catcher(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions);
}
自动捕捉到系统的异常,并且可以指定异常上报地址自动将运行错误上报给服务端。
flutter_swiper 轮播图
1. 添加依赖包,并下载(pubspec.yaml文件中dependencies下)
flutter_swiper: #lastversion
2. 导入包
import 'package:flutter_swiper/flutter_swiper.dart';
3. 使用
var itemList=[
{
"url":"http://..."
},
{
"url":""http://..."
},
{
"url":"http://..."
},
];
// 通常会在外层使用Container、AspectRatio
Container(
child: AspectRatio(
aspectRatio: 16 / 9, // 宽高比例
child: Swiper(
itemBuilder: (BuildContext context, int index) {
// item项
return Image.network(
this.itemList[index]["url"],
fit: BoxFit.fill,
);
},
itemCount: 3, // item个数
pagination: new SwiperPagination(), // 分页器
control: new SwiperControl(), // 左右箭头
// viewportFraction: 0.8,
// scale: 0.9,
),
),
),
FocusedMenuHolder 长按
@override
Widget build(BuildContext context) {
return FocusedMenuHolder(
child: Container(
// 省略原列表元素构建代码
),
onPressed: () {
// 点击事件
_handlePressed(context);
},
// 长按菜单
menuItems: <FocusedMenuItem>[
FocusedMenuItem(
title: Text("查看详情"),
trailingIcon: Icon(Icons.remove_red_eye_outlined),
onPressed: () {
_handlePressed(context);
}),
FocusedMenuItem(
title: Text("取消"),
trailingIcon: Icon(Icons.cancel),
onPressed: () {},
),
FocusedMenuItem(
title: Text(
"删除",
style: TextStyle(color: Colors.redAccent),
),
trailingIcon: Icon(
Icons.delete,
color: Colors.redAccent,
),
onPressed: () {
handleDelete(dynamicEntity.id);
}),
],
);
}
flutter_easyloading 防重提交时的loading蒙版
_handleSubmit() async {
//...校验代码
EasyLoading.showInfo('请稍候...', maskType: EasyLoadingMaskType.black);
//...网络请求代码
EasyLoading.dismiss();
}
socket_io_client(WebSocket插件)
// autoConnect:创建后是否自动连接(默认:自动连接)
// forceNew:是否每次都新建Socket对象,false(默认)则使用旧链接
_socket = SocketIO.io('ws://$host:$port', <String, dynamic>{
'transports': ['websocket'],
'autoConnect': true,
'forceNew': true
});
socket的响应回调
1. onConnect(EventHandler handler)
连接建立成功回调;
2. onConnectTimeout(EventHandler handler)
连接超时回调;
3. onConnectError(EventHandler handler)
连接错误回调;
4. onError(EventHandler handler)
发生错误时回调;
5. on(String event, (EventHandler handler)
订阅指定事件的消息,服务端发送该事件的消息时可以在该函数接收。
6. onDisconnect(EventHandler handler)
断开连接时回调;
7. connect/disconnect
主动连接/断开连接方法;
8. open/close
打开和关闭方法。
示例(与服务端通信)
class StreamSocket {
// StreamController 只允许有一个订阅者,可以使用 sink 属性的 add 方法添加新的流数据,完成添加后会通知其订阅者有新的数据产生。
final _socketResponse = StreamController<String>();
Stream<String> get getResponse => _socketResponse.stream;
final String host;
final int port;
late final Socket _socket;
StreamSocket({required this.host, required this.port}) {
_socket = SocketIO.io('ws://$host:$port', <String, dynamic>{
'transports': ['websocket'],
'autoConnect': true,
'forceNew': true
});
}
void connectAndListen() {
_socket.onConnect((_) {
debugPrint('connected');
});
_socket.onConnectTimeout((data) => debugPrint('timeout'));
_socket.onConnectError((error) => debugPrint(error.toString()));
_socket.onError((error) => debugPrint(error.toString()));
_socket.on('msg', (data) {
_socketResponse.sink.add(data);
});
_socket.onDisconnect((_) => debugPrint('disconnect'));
}
void sendTextMessage(String message) {
_socket.emit('msg', message);
}
void close() {
_socketResponse.close();
_socket.disconnect().close();
}
}
class _SocketClientWrapperState extends State<SocketClientWrapper> {
final StreamSocket streamSocket = StreamSocket(host: '127.0.0.1', port: 3001);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stream Provicer'),
),
body: Stack(
alignment: Alignment.bottomCenter,
children: [
StreamProvider<String>(
create: (context) => streamSocket.getResponse,
initialData: '',
child: StreamDemo(),
),
ChangeNotifierProvider<MessageModel>(
child: MessageReplyBar(messageSendHandler: (message) {
streamSocket.sendTextMessage(message);
}),
create: (context) => MessageModel(),
),
],
),
);
}
@override
void initState() {
streamSocket.connectAndListen();
super.initState();
}
@override
void dispose() {
streamSocket.close();
super.dispose();
}
}
示例(与其他客户端通信)
即时聊天
1. 在建立连接后,客户端发送消息将用户唯一标识符与连接的socket对象进行绑定。
2. 当其他用户发送消息给该用户时,找到该用户绑定的socket对象,再通过该socket的emit方法发送消息。
class StreamSocket<T> {
final _socketResponse = StreamController<T>();
Stream<T> get getResponse => _socketResponse.stream;
final String host;
final int port;
late final Socket _socket;
final String recvEvent;
StreamSocket(
{required this.host, required this.port, required this.recvEvent}) {
_socket = SocketIO.io('ws://$host:$port', <String, dynamic>{
'transports': ['websocket'],
'autoConnect': true,
'forceNew': true
});
}
void connectAndListen() {
_socket.onConnect((_) {
debugPrint('connected');
});
_socket.onConnectTimeout((data) => debugPrint('timeout'));
_socket.onConnectError((error) => debugPrint(error.toString()));
_socket.onError((error) => debugPrint(error.toString()));
_socket.on(recvEvent, (data) {
_socketResponse.sink.add(data);
});
_socket.onDisconnect((_) => debugPrint('disconnect'));
}
void regsiter(String userId) {
_socket.emit('register', userId);
}
void unregsiter(String userId) {
_socket.emit('unregister', userId);
}
void sendMessage(String event, T message) {
_socket.emit(event, message);
}
void close() {
_socketResponse.close();
_socket.disconnect().close();
}
}
class _ChatWithUserPageState extends State<ChatWithUserPage> {
late final StreamSocket<Map<String, dynamic>> streamSocket;
@override
void initState() {
super.initState();
streamSocket =
StreamSocket(host: '127.0.0.1', port: 3001, recvEvent: 'chat');
streamSocket.connectAndListen();
streamSocket.regsiter('user1');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('即时聊天'),
),
body: Stack(
alignment: Alignment.bottomCenter,
children: [
StreamProvider<Map<String, dynamic>?>(
create: (context) => streamSocket.getResponse,
initialData: null,
child: StreamDemo(),
),
ChangeNotifierProvider<MessageModel>(
child: MessageReplyBar(messageSendHandler: (message) {
Map<String, String> json = {
'fromUserId': 'user1',
'toUserId': 'user2',
'contentType': 'text',
'content': message
};
streamSocket.sendMessage('chat', json);
}),
create: (context) => MessageModel(),
),
],
),
);
}
@override
void dispose() {
streamSocket.unregsiter('user1');
streamSocket.close();
super.dispose();
}
}
fl_chart 图表插件(线条图、柱状图、散点图、饼图)
LineChartData 曲线图
1. lineTouchData
数据触点配置数据,即触摸到数据点后如何显示
2. gridData
网格数据
3. titlesData
上下左右四个方位的标题栏(坐标轴栏)数据,可以根据自己需要配置坐标轴显示以及标题;
4. borderData
边框数据,及是否要显示边框,以及如何显示边框;
5. lineBarsData
数据点数组,可以在同一个图表中显示多条曲线。
6. minX,maxX,minY和 MaxY
X 轴和 Y 轴的最大最小值范围,保证曲线显示在屏幕范围内。
示例
class LineChartDemo extends StatelessWidget {
LineChartDemo({Key? key}) : super(key: key);
final ChartStore store = ChartStore();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('曲线')),
body: Observer(
builder: (context) => LineChart(
sampleData1(store.lineYData),
swapAnimationDuration: const Duration(milliseconds: 250),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
store.featchLineData();
},
child: Icon(Icons.refresh, color: Colors.white),
),
);
}
// 曲线图
LineChartData sampleData1(List<double> yData) => LineChartData(
lineTouchData: lineTouchData1,
gridData: gridData,
titlesData: titlesData1,
borderData: borderData,
lineBarsData: lineBarsData1(yData),
minX: 0,
maxX: 15,
maxY: yData.reduce((value, element) => value < element ? element : value).toDouble()+1.0,
minY: yData.reduce((value, element) => value > element ? element : value).toDouble()-1.0,
);
// 曲线数据点
List<LineChartBarData> lineBarsData1(List<double> yData) {
double x = 1;
return [
LineChartBarData(
// 曲线还是折线
isCurved: true,
// 线条颜色
colors: [const Color(0xff4a99fa)],
// 线粗细
barWidth: 2,
// 是否圆形笔头
isStrokeCapRound: true,
// 是否显示点数据
dotData: FlDotData(show: true),
// 图形覆盖区域是否显示
belowBarData: BarAreaData(show: true),
// 坐标(x,y) 点集合
spots: yData.map((value) {
FlSpot spot = FlSpot(x, value.toDouble());
x += 2;
return spot;
}).toList(),
),
];
}
}
//
import 'package:mobx/mobx.dart';
import 'chart_service.dart';
part 'chart_store.g.dart';
class ChartStore = ChartStoreBase with _$ChartStore;
abstract class ChartStoreBase with Store {
@observable
List<double> lineYData = [0, 0, 0, 0, 0, 0, 0];
@action
Future<void> featchLineData() async {
var response = await ChartService.getLines();
if (response?.statusCode == 200) {
lineYData =
List<double>.from(response.data.map((e) => e.toDouble()).toList());
}
}
}
functional_widget 自动生成类组件
motion_toast 弹框
特性
可以通过动画图标实现动效;
内置了成功、警告、错误、提醒和删除类型;
支持自定义;
支持不同的主题色;
支持 null safety;
心跳动画效果;
完全自定义的文本内容;
内置动画效果;
支持自定义布局(LTR 和 RTL);
自定义持续时长;
自定义展现位置(居中,底部或顶部);
支持长文本显示;
自定义背景样式;
自定义消失形式。
库源码不多,可自行阅读。
使用
MotionToast.success(description: 'hello world').show(context);
内置Toast
支持修改默认参数(标题、位置、宽度、显示位置、动画曲线等)
// 错误提示
MotionToast.error(
description: '发生错误!',
width: 300,
position: MOTION_TOAST_POSITION.center,
).show(context);
// 删除提示
MotionToast.delete(
description: '已成功删除',
position: MOTION_TOAST_POSITION.bottom,
animationType: ANIMATION.fromLeft,
animationCurve: Curves.bounceIn,
).show(context);
// 信息提醒(带标题)
MotionToast.info(
description: '这是一条提醒,可能会有很多行。toast 会自动调整高度显示',
title: '提醒',
titleStyle: TextStyle(fontWeight: FontWeight.bold),
position: MOTION_TOAST_POSITION.bottom, // position和animationType存在一定互斥关系
animationType: ANIMATION.fromBottom,
animationCurve: Curves.linear,
dismissable: true, // 只对显示位置在底部时有用,点击空白处提前消失toast。
).show(context);
自定义Toast(使用MotionToast构建一个实例)
MotionToast(
title:'', // 标题
description: '这是自定义 toast', // 必传
icon: Icons.flag, // 必传。图标,IconData 类,可以使用系统字体图标。
primaryColor: Colors.blue, // 必传。主颜色(即大背景色)
secondaryColor: Colors.green[300], // 辅助色(图标和旁边的竖条的颜色)
titleStyle: TextStyle( // 标题的字体样式
color: Colors.white,
),
descriptionStyle: TextStyle( // toast文字的字体样式
color: Colors.white,
),
toastDuration: 3000, // 持续时间
backgroundType: , // 背景类型,枚举:transparent,solid和 lighter(默认)。
position: MOTION_TOAST_POSITION.center, // 显示位置
animationType: ANIMATION.fromRight, //
animationCurve: Curves.easeIn, // 动画曲线
onClose:, // 关闭时的回调
).show(context);
网友评论