美文网首页跨平台
Flutter了解之入门篇4-1(常用三方库)

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

作者: 平安喜乐698 | 来源:发表于2022-09-19 17:48 被阅读0次
      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);
    

    相关文章

      网友评论

        本文标题:Flutter了解之入门篇4-1(常用三方库)

        本文链接:https://www.haomeiwen.com/subject/ssocortx.html