跨平台

Flutter了解之入门篇4-1(常用三方库)

2022-09-19  本文已影响0人  平安喜乐698
  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);
上一篇 下一篇

猜你喜欢

热点阅读