FLutter flutter_easyrefresh 刷新组件

关键字 :Flutter APP

正如名字一样,EasyRefresh 很容易就能在 Flutter 应用上实现下拉刷新以及上拉加载操作,它支持几乎所有的 Flutter 控件。它的功能与 Android 的SmartRefreshLayout 很相似,同样也吸取了很多三方库的优点。EasyRefresh 中集成了多种风格的 Header 和 Footer,但是它并没有局限性,你可以很轻松的自定义。使用 Flutter 强大的动画,甚至随便一个简单的控件也可以完成。EasyRefresh 的目标是为 Flutter 打造一个强大,稳定,成熟的下拉刷新框架。

文档简单用例

1.在 pubspec.yaml 中添加依赖

//pub 方式
dependencies:
 flutter_easyrefresh: ^2.1.7

//导入方式
dependencies:
 flutter_easyrefresh:
   path: 项目路径

//git 方式
dependencies:
 flutter_easyrefresh:
   git:
     url: git://github.com/xuelongqy/flutter_easyrefresh.git

2.在布局文件中添加 EasyreFresh


import
'package:flutter_easyrefresh/easy_refresh.dart';

....
 // 方式一
 EasyRefresh(
   child: ScrollView(),
   onRefresh: () async{
    ....
  },
   onLoad: () async {
    ....
  },
)
 // 方式二
 EasyRefresh.custom(
   slivers: <Widget>[],
   onRefresh: () async{
    ....
  },
   onLoad: () async {
    ....
  },
)
 // 方式三
 EasyRefresh.builder(
   builder: (context, physics, header, footer) {
     return CustomScrollView(
       physics: physics,
       slivers: <Widget>[
        ...
         header,
        ...
         footer,
      ],
    );
  }
   onRefresh: () async{
    ....
  },
   onLoad: () async {
    ....
  },
)

3.触发刷新和加载动作

 
  EasyRefreshController _controller = EasyRefreshController();
....
EasyRefresh(
  controller: _controller,
  ....
);
....
_controller.callRefresh();
_controller.callLoad();

4.控制加载和刷新完成

 
  EasyRefreshController _controller = EasyRefreshController();
....
 EasyRefresh(
enableControlFinishRefresh: true,
enableControlFinishLoad: true,
  ....
);
....
 _controller.finishRefresh(success: true);
 _controller.finishLoad(success: true, noMore: false);

使用指定的 Header 和 Footer

 
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_easyrefresh/material_header.dart';
import 'package:flutter_easyrefresh/material_footer.dart';
....
 new EasyRefresh(
   header: MaterialHeader(),
   footer: MaterialFooter(),
   child: ScrollView(),
  ....
)

添加国际化支持

不提供自带国际化支持,请自行设置 ClassicalHeader 和 ClassicalFooter 中需要展示的文字。

实例

流程:

下拉刷新 onRefresh

只要在 onRefresh 方法里面,作数据下载的动作,下载完数据后,把值给 files.

上拉加载更多 onLoad

这个复杂一点,不过理清了就简单。在首次 onRefresh 加载数据给 files 时,先确定好每一次下载的数量。之后,每次调用 onLoad 下载追加到 files 后面。计算最后的数据。加载完后要显示的提示。

import 'dart:io';
import 'package:defensor/generated/l10n.dart';
import 'package:defensor/models/select.dart';
import 'package:defensor/service/device_service.dart';
import 'package:defensor/utils/tools.dart';
import 'package:defensor/views/common/gallery/photo_view.dart';
import 'package:defensor/views/common/gallery/video_view.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_thumbnail/video_thumbnail.dart';

class GalleryList extends StatefulWidget {
 /// 是否在线相册
 bool isOnline;

 /// 目录名
 String dirName;

 /// 目录数量
 String dirsSum;

 GalleryList(this.isOnline, this.dirName, this.dirsSum);

 @override
 _GalleryListState createState() => _GalleryListState();
}

class _GalleryListState extends State<GalleryList> {
 /// 视频文件列表
 List<String> files;

 /// 视频缩略图
 List<Image> videoThumbnails;

 /// 视频文件列表临时文件
 List<String> filesTemp;

 /// 视频缩略图临时文件
 List<Image> videoThumbnailsTemp;

 /// 文件总数
 int sum;

 /// 每次加截数量
 int numOfPage = 24;

 /// 加载更新记录次数
 int tag = 0;

 /// 是否显示删除
 bool isShowDelete = false;

 String selectText = '选择';

 /// 删除文件下标
 List<SelectModel> selectData;

 @override
 void initState() {
   super.initState();
}

 @override
 void dispose() {
   clear();
   super.dispose();
}

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: Text(Tools.cameraDirToName(widget.dirName)),
       actions: [
         isShowDelete
             ? CupertinoButton(
                 child: Text(
                   '全删除',
                   style: TextStyle(color: Colors.white),
                ),
                 onPressed: () {
                   print('全删除');
                })
            : Container(),
         CupertinoButton(
             child: Text(
               selectText,
               style: TextStyle(color: Colors.white),
            ),
             onPressed: () {
               isShowDelete = !isShowDelete;
               if (isShowDelete) {
                 selectText = '取消';
              } else {
                 selectText = '选择';
              }
               setState(() {});
            })
      ],
    ),
     body: _body(),
  );
}

 Widget _body() {
   return Stack(
     alignment: AlignmentDirectional.center,
     children: [
       EasyRefresh(
           onRefresh: () async {
             if (widget.isOnline) {
               loadOnlineFiles();
            } else {
               loadLocalFiles();
            }
          },
           onLoad: () async {
             if (widget.isOnline) {
               loadMoreOnlineData();
            } else {
               loadMoreOfflineData();
            }
          },
           firstRefresh: true,
           child: _gridView()),
       Positioned(
           bottom: 20,
           child: isShowDelete
               ? RaisedButton(
                   color: Colors.blue,
                   highlightColor: Colors.blue[700],
                   colorBrightness: Brightness.dark,
                   splashColor: Colors.grey,
                   child: Text('删除'),
                   shape: RoundedRectangleBorder(
                       borderRadius: BorderRadius.circular(10.0)),
                   onPressed: () => print('删除'),
                )
              : Container())
    ],
  );
}

 Widget _gridView() {
   // 没有数据
   if (files == null) {
     return Center(child: Text(S.current.noVideos));
  }

   double boxWidth = 110;
   // 取得屏幕 size
   final size = MediaQuery.of(context).size;
   int crossAxisCount = size.width ~/ boxWidth;
   // 初始化
   selectData = List<SelectModel>();

   return GridView.builder(
     shrinkWrap: true,
     gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
       crossAxisCount: crossAxisCount, // 一行多少个
       childAspectRatio: 1, // 宽高比
       crossAxisSpacing: 1, // 左右间隔
       mainAxisSpacing: 1, // 上下间隔
    ),
     itemCount: files.length ?? 0,
     itemBuilder: (context, index) {
       return InkWell(
         onTap: () => isShowDelete ? print('selected') : toView(files[index]),
         child: Stack(
           fit: StackFit.expand,
           children: [
             Container(
                 margin: EdgeInsets.all(1.0),
                 decoration: new BoxDecoration(
                    ),
                 child: videoThumbnails.length == 0
                     ? Image.asset('assets/images/gallery.png')
                    : videoThumbnails[index]),
          ],
        ),

      );
    },
  );
}

 /// 跳转浏览器
 toView(String url) {
   if (url.split('.').last != 'MP4') {
     Navigator.push(context,
         MaterialPageRoute(builder: (context) => PhotoViews(url: url)));
  } else {
     Navigator.push(context,
         MaterialPageRoute(builder: (context) => VideoView(url: url)));
  }
}

 /// 判断是视频还是图片,并转换为图片
 Future<List<Image>> videoToPhoto(List<String> filesPath) async {
   var list = List<Image>();
   var uint8list;
   for (var file in filesPath) {
     // specify the width of the thumbnail, let the height auto-scaled to keep the source aspect ratio
     // 判断为图片
     if (file.split('.').last != 'MP4') {
       // 网络图片
       if (file.split('/').first.contains('http')) {
         list.add(Image.network(file, fit: BoxFit.cover));
      } else {
         // file 图片
         list.add(Image.file(File(file), fit: BoxFit.cover));
      }
    } else {
       // 判断为视频并转为图片
       try {
         if (widget.isOnline) {
           // Generate a thumbnail file from video URL
           print(file);
           final thumbnailPath = await VideoThumbnail.thumbnailFile(
             video: file,
             thumbnailPath: (await getTemporaryDirectory()).path,
             imageFormat: ImageFormat.JPEG,
             maxHeight: 300,
             quality: 75,
          );
           // print('thumbnailPath $thumbnailPath');
           uint8list = File(thumbnailPath).readAsBytesSync();
        } else {
           // Generate a thumbnail in memory from video file
           uint8list = await VideoThumbnail.thumbnailData(
             video: file,
             imageFormat: ImageFormat.JPEG,
             maxWidth: 300,
             quality: 75,
          );
        }
         list.add(Image.memory(uint8list, fit: BoxFit.cover));
      } catch (e) {
         print(e);
      }
    }
  }
   return list;
}

 /// 清空数据
 clear() {
   files = null;
   filesTemp = null;
   videoThumbnails = null;
   videoThumbnailsTemp = null;
}

 /******************** 在线相册 ********************/
 /// 加载在线文件
 loadOnlineFiles() async {
   sum = int.parse(await DeviceService.getdirfilecount(dir: widget.dirName));
   print('sum $sum');
   var start = 0.toString();
   var end = (numOfPage - 1).toString();
   tag = 0;
   List<String> list = await DeviceService.getdirfilelist(
       dir: widget.dirName, start: start, end: end);
   if (list == null) {
     return;
  }

   // 判断是视频还是图片,并转换为图片
   List<Image> photos = await videoToPhoto(list);

   setState(() {
     files = list;
     videoThumbnails = photos;
  });
   tag++;

   print('files: ${files.length}');
}

 /// 加载更多数据
 loadMoreOnlineData() async {
   // 最大加载次数 36/5 = 8次
   var sum = int.parse(widget.dirsSum);
   var tagMax = (sum / numOfPage) > (sum ~/ numOfPage)
       ? sum ~/ numOfPage + 1
      : sum ~/ numOfPage;
   // print('tagMax $tagMax');
   // 加载完, 返回
   if (tag >= tagMax) {
     return;
  }
   // 0-4 5-9 10-14, 15-19, 20-24, 25-29...
   // 计算 start, end
   var start = tag * numOfPage;
   var end;
   if (tag == tagMax - 1) {
     // 最后一次
     end = (tag * numOfPage) + (sum % numOfPage) - 1;
  } else {
     end = (tag + 1) * numOfPage - 1;
  }
   // print('start $start end $end');

   List<String> list = await DeviceService.getdirfilelist(
       dir: widget.dirName, start: start.toString(), end: end.toString());
   if (list == null) {
     return;
  }

   // 视频文件转图片
   var photos = await videoToPhoto(list);
   print('photos $photos');
   setState(() {
     files.addAll(list);
     videoThumbnails.addAll(photos);
  });

   // print('files ${files.length}');

   tag++;
}

 /******************** 本地相册 ********************/
 /// 加载本地文件
 loadLocalFiles() async {
   // 视频目录
   Directory video;
   try {
     // Android
     if (Platform.isAndroid) {
       // 获取文档目录的路径 // /storage/emulated/0/Android/data/com.wpgholding.defensor/files
       video = await getExternalStorageDirectory();
    }
     // iOS
     if (Platform.isIOS) {
       // 获取文档目录的路径
       video = await getApplicationDocumentsDirectory();
       // 拼接文档目录上的video地址
    }
     // 拼接 video 地址
     video = Directory(video.path + '/video');
     // print('本地视频目录: $video');
     bool exists = await video.exists();
     if (!exists) {
       await video.create();
    }
     var videos = video.listSync();
     var list = List<String>();
     videos.forEach((file) {
       list.add(file.path);
    });

     // 视频文件过滤
     filesTemp = imageFilter(list);

     // 视频文件转图片
     videoThumbnailsTemp = await videoToPhoto(list);

     /// 保存总数
     sum = filesTemp.length;
     tag = 0;
     print('sum $sum');

     setState(() {
       files = filesTemp.sublist(0, numOfPage);
       videoThumbnails = videoThumbnailsTemp.sublist(0, numOfPage);
    });
     // 记录一次
     tag++;
     // print('videoThumbnails $videoThumbnails');
  } catch (err) {
     print(err);
  }
}

 /// 加载更多数据
 loadMoreOfflineData() {
   // 最大加载次数 36/5 = 8次
   var tagMax = (sum / numOfPage) > (sum ~/ numOfPage)
       ? sum ~/ numOfPage + 1
      : sum ~/ numOfPage;
   // print('tagMax $tagMax');
   // 加载完, 返回
   if (tag >= tagMax) {
     return;
  }
   // 0-5 5-10 10-15, 15-20, 20-25, 25-30, 30-35, 35-36...
   // 计算 start, end
   var start = tag * numOfPage;
   var end;
   if (tag == tagMax - 1) {
     // 最后一次
     end = (tag * numOfPage) + (sum % numOfPage);
  } else {
     end = (tag + 1) * numOfPage;
  }
   // print('start $start end $end');
   setState(() {
     files.addAll(filesTemp.sublist(start, end));
     videoThumbnails.addAll(videoThumbnailsTemp.sublist(start, end));
  });
   tag++;
   // print('files ${files.length} videoThumbnails ${videoThumbnails.length}');
}

 /// 视频文件过滤
 List<String> imageFilter(List<String> files) {
   var newFiles = List<String>();
   files.forEach((element) {
     // 保留视频格式后辍 mp4
     var suffix = element.split('.').last.toLowerCase();
     if (suffix == 'mp4') {
       newFiles.add(element);
    }
  });
   return newFiles;
}
}

参考资料

flutter_easyrefresh (https://github.com/xuelongqy/flutter_easyrefresh)

 

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论