package_info 獲取應(yīng)用相關(guān)信息(版本號、應(yīng)用名、包名、構(gòu)建版本號等)
url_launcher 跳轉(zhuǎn)到其他應(yīng)用/瀏覽器
flutter_easyrefresh 上下拉刷新
refresh_indicator 上下拉刷新
logging 日志打印
shared_preferences 持久化鍵值對
sqflite 持久化數(shù)據(jù)庫
marquee 滾動文字組件
carousel_slider
cached_network_image 網(wǎng)絡(luò)圖片 加載緩存
flutter_cache_manager
video_player
path_provider 文件管理
cookie_jar
catcher 全局異常捕獲
flutter_swiper 輪播圖
FocusedMenuHolder 長按
flutter_easyloading 防重提交時的loading蒙版
socket_io_client(WebSocket插件)
fl_chart 圖表插件(線條圖、柱狀圖、散點(diǎn)圖、餅圖)
functional_widget 自動生成類組件
motion_toast 彈框
package_info 獲取應(yīng)用相關(guān)信息(版本號、應(yīng)用名、包名、構(gòu)建版本號等)
import 'package:package_info/package_info.dart';
PackageInfo.fromPlatform().then((packageInfo){
});
注意:
1. 如果沒有給設(shè)置iOS的displayName會導(dǎo)致獲取不到appName,運(yùn)行會提示出錯。
url_launcher 跳轉(zhuǎn)到其他應(yīng)用/瀏覽器
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
可用來存儲:布爾值、整型、浮點(diǎn)型、字符串、字符串?dāng)?shù)組。對象需要做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 持久化數(shù)據(jù)庫
// 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 滾動文字組件
支持設(shè)置文字的多種參數(shù)(如字體,滾動方向,留白,滾動速度)
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,
)
carousel_slider 輪播圖
cached_network_image 網(wǎng)絡(luò)圖片 加載緩存
flutter_cache_manager
video_player
path_provider 文件管理
getApplicationDocumentsDirectory
應(yīng)用文檔目錄(不會被系統(tǒng)清除,用戶數(shù)據(jù)存儲目錄),對于安卓推薦使用外部存儲getExternalStorageDirectory。
getLibraryDirectory
指向應(yīng)用可以持久存儲數(shù)據(jù)的目錄,不支持安卓平臺。
getTemporaryDirectory
應(yīng)用臨時目錄(可能被清除)
getApplicationSupportDirectory
應(yīng)用支持目錄,一般放置與用戶無關(guān)的數(shù)據(jù)。
getExternalStorageDirectory
獲取外部存儲目錄,不支持 iOS 平臺。
getExternalCacheDirectories
獲取外部緩存目錄,,不支持 iOS 平臺。
getExternalStorageDirectories
獲取外部的目錄列表,不支持 iOS 平臺。
getDownloadsDirectory
獲取下載目錄,用于 Web 端,不支持安卓和 iOS平臺。
例
getTemporaryDirectory().then((tempDir) => {_destPath = tempDir.path + 'googlechrome.dmg'});
1. OS Error: Read-only file system
安卓系統(tǒng)需要獲取READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE權(quán)限。
2. onReceivedProgress中total=-1
表示該文件被壓縮或者需要會話信息才可以下載(如后端開啟了驗(yàn)證)。
3. 刪除文件的時候需要檢查文件是否在下載過程中,如果在下載過程中刪除會引起文件讀寫沖突,拋出異常。
4. CancelToken一個實(shí)例只能取消一次請求
因此每次發(fā)起請求的時候需要重新構(gòu)建CancelToken對象,否則取消一次后無法再次取消。
cookie_jar
catcher 全局異常捕獲
import 'package:catcher/catcher.dart';
main() {
// 1.
// 創(chuàng)建debugCatcher,錯誤發(fā)生后會彈框,并輸出到控制臺
CatcherOptions debugOptions =
CatcherOptions(DialogReportMode(), [ConsoleHandler()]);
// 創(chuàng)建releaseCatcher,錯誤發(fā)生后會彈框,并將錯誤發(fā)送到郵箱
CatcherOptions releaseOptions = CatcherOptions(DialogReportMode(), [
EmailManualHandler(["support@email.com"])
]);
// 2.
Catcher(rootWidget: MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions);
}
自動捕捉到系統(tǒng)的異常,并且可以指定異常上報(bào)地址自動將運(yùn)行錯誤上報(bào)給服務(wù)端。
flutter_swiper 輪播圖
1. 添加依賴包,并下載(pubspec.yaml文件中dependencies下)
flutter_swiper: #lastversion
2. 導(dǎo)入包
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項(xiàng)
return Image.network(
this.itemList[index]["url"],
fit: BoxFit.fill,
);
},
itemCount: 3, // item個數(shù)
pagination: new SwiperPagination(), // 分頁器
control: new SwiperControl(), // 左右箭頭
// viewportFraction: 0.8,
// scale: 0.9,
),
),
),
FocusedMenuHolder 長按
@override
Widget build(BuildContext context) {
return FocusedMenuHolder(
child: Container(
// 省略原列表元素構(gòu)建代碼
),
onPressed: () {
// 點(diǎn)擊事件
_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 {
//...校驗(yàn)代碼
EasyLoading.showInfo('請稍候...', maskType: EasyLoadingMaskType.black);
//...網(wǎng)絡(luò)請求代碼
EasyLoading.dismiss();
}
socket_io_client(WebSocket插件,對Dart的Socket進(jìn)行了封裝)
創(chuàng)建連接
// autoConnect:創(chuàng)建后是否自動連接(默認(rèn):自動連接)。forceNew:是否每次都新建Socket對象,false(默認(rèn))則使用舊鏈接
_socket = SocketIO.io('ws://$host:$port', <String, dynamic>{
'transports': ['websocket'],
'autoConnect': true,
'forceNew': true
});
socket常用的響應(yīng)回調(diào)
1. onConnect(EventHandler handler)
連接建立成功回調(diào);
2. onConnectTimeout(EventHandler handler)
連接超時回調(diào);
3. onConnectError(EventHandler handler)
連接錯誤回調(diào);
4. onError(EventHandler handler)
發(fā)生錯誤時回調(diào);
5. on(String event, (EventHandler handler)
訂閱指定事件的消息,服務(wù)端發(fā)送該事件的消息時可以在該函數(shù)接收。
6. onDisconnect(EventHandler handler)
斷開連接時回調(diào);
7. connect/disconnect
主動連接/斷開連接方法;
8. open/close
打開和關(guān)閉方法。
示例(與服務(wù)端通信)
class StreamSocket {
// StreamController 只允許有一個訂閱者,可以使用 sink 屬性的 add 方法添加新的流數(shù)據(jù),完成添加后會通知其訂閱者(StreamProvider)有新的數(shù)據(jù)產(chǎn)生。
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. 在建立連接后,客戶端發(fā)送消息將用戶唯一標(biāo)識符與連接的socket對象進(jìn)行綁定。
2. 當(dāng)其他用戶發(fā)送消息給該用戶時,找到該用戶綁定的socket對象,再通過該socket的emit方法發(fā)送消息。
/*
Socket可以發(fā)送字符串或Json 對象。
約定消息聊天為Json對象,字段如下:
fromUserId:消息來源用戶 id;
toUserId:接收消息用戶 id;
contentType:消息類型,文本、圖片、語音、視頻等。
content:消息內(nèi)容。
*/
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); // 會調(diào)用對象的 toJson 將對象轉(zhuǎn)為 Json 對象發(fā)送,因此泛型的類需要實(shí)現(xiàn) Map<String dynamic> toJson 方法。
}
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 圖表插件(線條圖、柱狀圖、散點(diǎn)圖、餅圖)
LineChartData 曲線圖
1. lineTouchData
數(shù)據(jù)觸點(diǎn)配置數(shù)據(jù),即觸摸到數(shù)據(jù)點(diǎn)后如何顯示
2. gridData
網(wǎng)格數(shù)據(jù)
3. titlesData
上下左右四個方位的標(biāo)題欄(坐標(biāo)軸欄)數(shù)據(jù),可以根據(jù)自己需要配置坐標(biāo)軸顯示以及標(biāo)題;
4. borderData
邊框數(shù)據(jù),及是否要顯示邊框,以及如何顯示邊框;
5. lineBarsData
數(shù)據(jù)點(diǎn)數(shù)組,可以在同一個圖表中顯示多條曲線。
6. minX,maxX,minY和 MaxY
X 軸和 Y 軸的最大最小值范圍,保證曲線顯示在屏幕范圍內(nèi)。
示例(使用到MobX)
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,
);
// 曲線數(shù)據(jù)點(diǎn)
List<LineChartBarData> lineBarsData1(List<double> yData) {
double x = 1;
return [
LineChartBarData(
// 曲線還是折線
isCurved: true,
// 線條顏色
colors: [const Color(0xff4a99fa)],
// 線粗細(xì)
barWidth: 2,
// 是否圓形筆頭
isStrokeCapRound: true,
// 是否顯示點(diǎn)數(shù)據(jù)
dotData: FlDotData(show: true),
// 圖形覆蓋區(qū)域是否顯示
belowBarData: BarAreaData(show: true),
// 坐標(biāo)(x,y) 點(diǎn)集合
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 彈框
特性
可以通過動畫圖標(biāo)實(shí)現(xiàn)動效;
內(nèi)置了成功、警告、錯誤、提醒和刪除類型;
支持自定義;
支持不同的主題色;
支持 null safety;
心跳動畫效果;
完全自定義的文本內(nèi)容;
內(nèi)置動畫效果;
支持自定義布局(LTR 和 RTL);
自定義持續(xù)時長;
自定義展現(xiàn)位置(居中,底部或頂部);
支持長文本顯示;
自定義背景樣式;
自定義消失形式。
使用
// 成功提示
MotionToast.success(description: 'hello world').show(context);
內(nèi)置Toast
支持修改默認(rèn)參數(shù)(標(biāo)題、位置、寬度、顯示位置、動畫曲線等)
// 錯誤提示
MotionToast.error(
description: '發(fā)生錯誤!',
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);
// 信息提醒(帶標(biāo)題)
MotionToast.info(
description: '這是一條提醒,可能會有很多行。toast 會自動調(diào)整高度顯示',
title: '提醒',
titleStyle: TextStyle(fontWeight: FontWeight.bold),
position: MOTION_TOAST_POSITION.bottom, // position和animationType存在一定互斥關(guān)系
animationType: ANIMATION.fromBottom,
animationCurve: Curves.linear,
dismissable: true, // 只對顯示位置在底部時有用,為true時點(diǎn)擊空白處提前消失toast。
).show(context);
自定義Toast
使用MotionToast構(gòu)建一個實(shí)例。
MotionToast(
title:'', // 標(biāo)題
description: '這是自定義 toast', // 必傳
icon: Icons.flag, // 必傳。圖標(biāo),IconData 類,可以使用系統(tǒng)字體圖標(biāo)。
primaryColor: Colors.blue, // 必傳。主顏色(即大背景色)
secondaryColor: Colors.green[300], // 輔助色(圖標(biāo)和旁邊的豎條的顏色)
titleStyle: TextStyle( // 標(biāo)題的字體樣式
color: Colors.white,
),
descriptionStyle: TextStyle( // toast文字的字體樣式
color: Colors.white,
),
toastDuration: 3000, // 持續(xù)時間
backgroundType: , // 背景類型,枚舉:transparent,solid和 lighter(默認(rèn))。
position: MOTION_TOAST_POSITION.center, // 顯示位置
animationType: ANIMATION.fromRight, // 動畫方向
animationCurve: Curves.easeIn, // 動畫曲線
onClose:, // 關(guān)閉時會調(diào)用
).show(context);