技术选型
项目中需要定制一个基于Gif图片的下拉刷新功能,调研发现
Flutter支持上下拉刷新的框架很多,其中有两个比较有名的有flutter_easyrefresh和pull_to_refresh,两个框架功能都很强大,都能满足需求,其中flutter_easyrefresh在github的star更多,lib包大小为644KB
,pull_to_refresh在pub.dev的评分更高,lib包大小为172KB
,综合考虑后,选择基于pull_to_refresh来实现框架功能。
下拉刷新GIF图片的生成
下拉刷新需要控制Gif图片的播放,所以需要引入组件flutter_gifimage,gifimage支持加载本地和网络的gif图片,但是不支持加载图片列表的方式来执行gif动画,所以我们需要将图片列表生成为gif图片(UI小姐姐只给了图片列表)。
网上有很多网站可以生成gif,但是都有图片数量限制。 下载一个生成gif的软件来生成又显得很麻烦,我们选择使用Python的Pillow库来创建gif图片。Pillow是PIL的python3版本,功能强大,可以很好的完成需求。创建脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from PIL import Image, ImageDraw
def gen_frame(path): png = Image.open(path).convert('RGBA') background = Image.new('RGBA', png.size, (255,255,255,0)) alpha_composite = Image.alpha_composite(background, png) return alpha_composite
image_list = [] im0 = gen_frame('refresh_images/Loading_00@2x.png') for i in range(1,57): path = 'refresh_images/Loading_0' + str(i) + "@2x.png" image_list.append(gen_frame(path))
im0.save('GIF.gif', save_all=True, append_images=image_list, loop=0, duration=34, transparency=0, disposal=2)
|
上述脚本对Gif背景进行了处理,以生成一张透明背景的gif图片
Gif下拉刷新组件头部的实现
pull_to_refresh中提供了抽象类RefreshIndicator与RefreshIndicator,与material提供的重名,所以需要隐藏。 import部分代码如下:
1 2 3
| import 'package:flutter/material.dart' hide RefreshIndicator, RefreshIndicatorState; import 'package:pull_to_refresh/pull_to_refresh.dart';
|
最终下拉刷新的Header实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import 'package:flutter/material.dart' hide RefreshIndicator, RefreshIndicatorState; import 'package:flutter_gifimage/flutter_gifimage.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart';
class GifHeader extends RefreshIndicator { GifHeader() : super(height: 72.0, refreshStyle: RefreshStyle.Follow); @override _GifHeaderState createState() => _GifHeaderState(); }
class _GifHeaderState extends RefreshIndicatorState<GifHeader> with SingleTickerProviderStateMixin { GifController _gifController;
@override void initState() { _gifController = GifController( vsync: this, value: 0, ); super.initState(); }
@override void onModeChange(RefreshStatus mode) { if (mode == RefreshStatus.refreshing) { _gifController.repeat( min: 0, max: 44, period: Duration(milliseconds: 2000)); } super.onModeChange(mode); }
@override Future<void> endRefresh() { return _gifController.animateTo(44, duration: Duration(milliseconds: 500)); }
@override void resetValue() { _gifController.value = 0; super.resetValue(); }
@override Widget buildContent(BuildContext context, RefreshStatus mode) { return GifImage( image: AssetImage("images/pull_refresh.gif"), controller: _gifController, height: 72.0, ); }
@override void dispose() { _gifController.dispose(); super.dispose(); } }
|
设置Gif的value
(帧)时,不能超过Gif的最大帧数,不然超出的帧数是显示一个有颜色的空白页面
集成进入项目
pull_to_refresh提供了全局的统一配置类RefreshConfiguration
,用它来包裹MaterialApp则可以全局生效,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Widget build(BuildContext context) { return RefreshConfiguration( headerBuilder: () => GifHeader(), footerBuilder: () => ClassicFooter(), headerTriggerDistance: 72.0, maxOverScrollExtent: 100, maxUnderScrollExtent: 0, enableScrollWhenRefreshCompleted: true, enableLoadingWhenFailed: true, hideFooterWhenNotFull: false, shouldFooterFollowWhenNotFull: (status) => true, enableBallisticLoad: true, child: MaterialApp( title: 'Flutter Demo', home: HomePage(), ), ); }
|
全局配置好后,则可以在列表进行集成了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:my_flutter/message/notice_list_page/notice_ist_cell.dart'; import 'package:my_flutter/message/notice_list_page/notice_list_model.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart';
class NoticeListPage extends StatefulWidget { @override _NoticeListPageState createState() => _NoticeListPageState(); }
class _NoticeListPageState extends State<NoticeListPage> { RefreshController _refreshController = RefreshController(initialRefresh: true); List<NoticeListModel> list = [];
void _onRefresh() async { await Future.delayed(Duration(milliseconds: 2000)); _refreshController.refreshCompleted(); setState(() { list = _getList(); }); }
void _onLoading() async { await Future.delayed(Duration(milliseconds: 1000)); _refreshController.loadComplete(); if (mounted) setState(() { list.addAll(_getList()); }); }
@override Widget build(BuildContext context) { return Scaffold( body: SmartRefresher( enablePullDown: true, enablePullUp: list.length > 0, controller: _refreshController, onRefresh: _onRefresh, onLoading: _onLoading, child: ListView.builder( itemBuilder: (c, i) => NoticeListCell(model: list[i]), itemExtent: 100.0, itemCount: list.length, ), ), ); }
List<NoticeListModel> _getList() { return [NoticeListModel(),NoticeListModel()]; } }
|