前言
flutter中的下拉刷新,我们通常refreshindicator
,可以通过backgroundcolor
,color
或strokewidth
设置下拉刷新的颜色粗细等样式,但如果要自定义自己的widget,refreshindicator
并没有暴露出对应的属性,那如何修改呢?
1. 简单更改refreshindicator的样式
demo.dart
refreshindicator( backgroundcolor: colors.amber, // 滚动loading的背景色 color: colors.blue, // 滚动loading线条的颜色 strokewidth: 10, // 滚动loading的粗细 onrefresh: () async { await future.delayed(duration(seconds: 2)); }, child: center( child: singlechildscrollview( // 总是可以滚动,不能滚动时无法触发下拉刷新,因此设置为总是能滚动 physics: const alwaysscrollablescrollphysics(), // 滚动区域的内容 // child: , ), ), );
效果:
2. 自定义下拉loading的样式
查看
refreshindicator
的属性,我们可以发现并没有直接更改loading widget的方式。
- 我们查看源码,可以发现返回的
loading
主要是:refreshprogressindicator
和cupertinoactivityindicator
两种。
.../flutter/packages/flutter/lib/src/material/refresh_indicator.dart
- 以下是部分源码:
- 我们注释掉源码中
loading
的部分,改为自己定义的样式 - 如果要自定义进出动画的话可以在替换更高层的widget,这里只替换
animatedbuilder
下的widget
// 源码的最后部分,大概619行左右 child: animatedbuilder( animation: _positioncontroller, builder: (buildcontext context, widget? child) { // 以下widget就是下拉时显示的loading,我们注释掉 // final widget materialindicator = refreshprogressindicator( // semanticslabel: widget.semanticslabel ?? // materiallocalizations.of(context) // .refreshindicatorsemanticlabel, // semanticsvalue: widget.semanticsvalue, // value: showindeterminateindicator ? null : _value.value, // valuecolor: _valuecolor, // backgroundcolor: widget.backgroundcolor, // strokewidth: widget.strokewidth, // ); // final widget cupertinoindicator = // cupertinoactivityindicator( // color: widget.color, // ); // switch (widget._indicatortype) { // case _indicatortype.material: // return materialindicator; // case _indicatortype.adaptive: // { // final themedata theme = theme.of(context); // switch (theme.platform) { // case targetplatform.android: // case targetplatform.fuchsia: // case targetplatform.linux: // case targetplatform.windows: // return materialindicator; // case targetplatform.ios: // case targetplatform.macos: // return cupertinoindicator; // } // } // } // 改为自己定义的样式 return container( color: widget.color, width: 100, height: 100, child: text("loading"), ); }, ),
效果如下:
注:
- 直接修改源码会影响其他项目,且多人协作开发的话,其他人无法获得同样的效果的
- 本文的解决方案是将源码复制出来,重新命名后使用
2.1. 优化下拉回到顶部的时间
- 通过上面的效果,我们可以看到,下拉后,列表内容部分立即回到了顶部,这里希望刷新完成后,列表再回到顶部
最终效果:
2.1.1. 思路
- 先将源码拷贝出来,更改
widget
名称和flutter的refreshindicator
区分开,再在源码基础上进行修改 - 刷新顶部如何不回弹?顶部增加一个
sizedbox
占位,根据下拉高度更改sizedbox
占位的高度,在源码中_positioncontroller
可以获取到下拉的高度。 - 由于是滚动列表,因此使用
nestedscrollview
融合占位元素和滚动列表
2.1.2. 代码
- 以下是完整代码,有注释的部分才是修改部分
import 'dart:async'; import 'dart:math' as math; import 'package:flutter/foundation.dart' show clampdouble; import 'package:flutter/material.dart'; // =========修改下拉比例触发刷新,源码18行左右========= const double _kdragcontainerextentpercentage = 0.1; const double _kdragsizefactorlimit = 1; // =========修改下拉比例触发刷新========= const duration _kindicatorsnapduration = duration(milliseconds: 150); const duration _kindicatorscaleduration = duration(milliseconds: 200); typedef refreshcallback = future<void> function(); enum _refreshindicatormode { drag, // pointer is down. armed, // dragged far enough that an up event will run the onrefresh callback. snap, // animating to the indicator's final "displacement". refresh, // running the refresh callback. done, // animating the indicator's fade-out after refreshing. canceled, // animating the indicator's fade-out after not arming. } /// used to configure how [refreshindicator] can be triggered. enum refreshindicatortriggermode { anywhere, onedge, } enum _indicatortype { material, adaptive } // ======更改名字,源码119行左右====== class refreshwidget extends statefulwidget { const refreshwidget({ super.key, required this.child, this.displacement = 40.0, this.edgeoffset = 0.0, required this.onrefresh, this.color, this.backgroundcolor, this.notificationpredicate = defaultscrollnotificationpredicate, this.semanticslabel, this.semanticsvalue, this.strokewidth = refreshprogressindicator.defaultstrokewidth, this.triggermode = refreshindicatortriggermode.onedge, }) : _indicatortype = _indicatortype.material; const refreshwidget.adaptive({ super.key, required this.child, this.displacement = 40.0, this.edgeoffset = 0.0, required this.onrefresh, this.color, this.backgroundcolor, this.notificationpredicate = defaultscrollnotificationpredicate, this.semanticslabel, this.semanticsvalue, this.strokewidth = refreshprogressindicator.defaultstrokewidth, this.triggermode = refreshindicatortriggermode.onedge, }) : _indicatortype = _indicatortype.adaptive; final widget child; final double displacement; final double edgeoffset; final refreshcallback onrefresh; final color? color; final color? backgroundcolor; final scrollnotificationpredicate notificationpredicate; final string? semanticslabel; final string? semanticsvalue; final double strokewidth; final _indicatortype _indicatortype; final refreshindicatortriggermode triggermode; @override refreshwidgetstate createstate() => refreshwidgetstate(); } // 改名称,源码266行左右 class refreshwidgetstate extends state<refreshwidget> with tickerproviderstatemixin<refreshwidget> { late animationcontroller _positioncontroller; late animationcontroller _scalecontroller; late animation<double> _positionfactor; late animation<double> _scalefactor; late animation<double> _value; late animation<color?> _valuecolor; _refreshindicatormode? _mode; late future<void> _pendingrefreshfuture; bool? _isindicatorattop; double? _dragoffset; late color _effectivevaluecolor = widget.color ?? theme.of(context).colorscheme.primary; static final animatable<double> _threequartertween = tween<double>(begin: 0.0, end: 0.75); static final animatable<double> _kdragsizefactorlimittween = tween<double>(begin: 0.0, end: _kdragsizefactorlimit); static final animatable<double> _onetozerotween = tween<double>(begin: 1.0, end: 0.0); @override void initstate() { super.initstate(); _positioncontroller = animationcontroller(vsync: this); _positionfactor = _positioncontroller.drive(_kdragsizefactorlimittween); _value = _positioncontroller.drive( _threequartertween); // the "value" of the circular progress indicator during a drag. _scalecontroller = animationcontroller(vsync: this); _scalefactor = _scalecontroller.drive(_onetozerotween); } @override void didchangedependencies() { _setupcolortween(); super.didchangedependencies(); } @override void didupdatewidget(covariant refreshwidget oldwidget) { super.didupdatewidget(oldwidget); if (oldwidget.color != widget.color) { _setupcolortween(); } } @override void dispose() { _positioncontroller.dispose(); _scalecontroller.dispose(); super.dispose(); } void _setupcolortween() { // reset the current value color. _effectivevaluecolor = widget.color ?? theme.of(context).colorscheme.primary; final color color = _effectivevaluecolor; if (color.alpha == 0x00) { // set an always stopped animation instead of a driven tween. _valuecolor = alwaysstoppedanimation<color>(color); } else { // respect the alpha of the given color. _valuecolor = _positioncontroller.drive( colortween( begin: color.withalpha(0), end: color.withalpha(color.alpha), ).chain( curvetween( curve: const interval(0.0, 1.0 / _kdragsizefactorlimit), ), ), ); } } bool _shouldstart(scrollnotification notification) { return ((notification is scrollstartnotification && notification.dragdetails != null) || (notification is scrollupdatenotification && notification.dragdetails != null && widget.triggermode == refreshindicatortriggermode.anywhere)) && ((notification.metrics.axisdirection == axisdirection.up && notification.metrics.extentafter == 0.0) || (notification.metrics.axisdirection == axisdirection.down && notification.metrics.extentbefore == 0.0)) && _mode == null && _start(notification.metrics.axisdirection); } bool _handlescrollnotification(scrollnotification notification) { if (!widget.notificationpredicate(notification)) { return false; } if (_shouldstart(notification)) { setstate(() { _mode = _refreshindicatormode.drag; }); return false; } bool? indicatorattopnow; switch (notification.metrics.axisdirection) { case axisdirection.down: case axisdirection.up: indicatorattopnow = true; case axisdirection.left: case axisdirection.right: indicatorattopnow = null; } if (indicatorattopnow != _isindicatorattop) { if (_mode == _refreshindicatormode.drag || _mode == _refreshindicatormode.armed) { _dismiss(_refreshindicatormode.canceled); } } else if (notification is scrollupdatenotification) { if (_mode == _refreshindicatormode.drag || _mode == _refreshindicatormode.armed) { if ((notification.metrics.axisdirection == axisdirection.down && notification.metrics.extentbefore > 0.0) || (notification.metrics.axisdirection == axisdirection.up && notification.metrics.extentafter > 0.0)) { _dismiss(_refreshindicatormode.canceled); } else { if (notification.metrics.axisdirection == axisdirection.down) { _dragoffset = _dragoffset! - notification.scrolldelta!; } else if (notification.metrics.axisdirection == axisdirection.up) { _dragoffset = _dragoffset! + notification.scrolldelta!; } _checkdragoffset(notification.metrics.viewportdimension); } } if (_mode == _refreshindicatormode.armed && notification.dragdetails == null) { _show(); } } else if (notification is overscrollnotification) { if (_mode == _refreshindicatormode.drag || _mode == _refreshindicatormode.armed) { if (notification.metrics.axisdirection == axisdirection.down) { _dragoffset = _dragoffset! - notification.overscroll; } else if (notification.metrics.axisdirection == axisdirection.up) { _dragoffset = _dragoffset! + notification.overscroll; } _checkdragoffset(notification.metrics.viewportdimension); } } else if (notification is scrollendnotification) { switch (_mode) { case _refreshindicatormode.armed: _show(); case _refreshindicatormode.drag: _dismiss(_refreshindicatormode.canceled); case _refreshindicatormode.canceled: case _refreshindicatormode.done: case _refreshindicatormode.refresh: case _refreshindicatormode.snap: case null: // do nothing break; } } return false; } bool _handleindicatornotification( overscrollindicatornotification notification) { if (notification.depth != 0 || !notification.leading) { return false; } if (_mode == _refreshindicatormode.drag) { notification.disallowindicator(); return true; } return false; } bool _start(axisdirection direction) { assert(_mode == null); assert(_isindicatorattop == null); assert(_dragoffset == null); switch (direction) { case axisdirection.down: case axisdirection.up: _isindicatorattop = true; case axisdirection.left: case axisdirection.right: _isindicatorattop = null; return false; } _dragoffset = 0.0; _scalecontroller.value = 0.0; _positioncontroller.value = 0.0; return true; } void _checkdragoffset(double containerextent) { assert(_mode == _refreshindicatormode.drag || _mode == _refreshindicatormode.armed); double newvalue = _dragoffset! / (containerextent * _kdragcontainerextentpercentage); if (_mode == _refreshindicatormode.armed) { newvalue = math.max(newvalue, 1.0 / _kdragsizefactorlimit); } _positioncontroller.value = clampdouble(newvalue, 0.0, 1.0); // this triggers various rebuilds if (_mode == _refreshindicatormode.drag && _valuecolor.value!.alpha == _effectivevaluecolor.alpha) { _mode = _refreshindicatormode.armed; } } // stop showing the refresh indicator. future<void> _dismiss(_refreshindicatormode newmode) async { await future<void>.value(); assert(newmode == _refreshindicatormode.canceled || newmode == _refreshindicatormode.done); setstate(() { _mode = newmode; }); switch (_mode!) { // ===========刷新完成,需要将_positioncontroller置为0,源码498行左右========= case _refreshindicatormode.done: await future.wait([ _scalecontroller.animateto(1.0, duration: _kindicatorscaleduration), _positioncontroller.animateto(0.0, duration: _kindicatorscaleduration) ]); // ===========刷新完成,需要将_positioncontroller置为0========= case _refreshindicatormode.canceled: await _positioncontroller.animateto(0.0, duration: _kindicatorscaleduration); case _refreshindicatormode.armed: case _refreshindicatormode.drag: case _refreshindicatormode.refresh: case _refreshindicatormode.snap: assert(false); } if (mounted && _mode == newmode) { _dragoffset = null; _isindicatorattop = null; setstate(() { _mode = null; }); } } void _show() { assert(_mode != _refreshindicatormode.refresh); assert(_mode != _refreshindicatormode.snap); final completer<void> completer = completer<void>(); _pendingrefreshfuture = completer.future; _mode = _refreshindicatormode.snap; _positioncontroller .animateto(1.0 / _kdragsizefactorlimit, duration: _kindicatorsnapduration) .then<void>((void value) { if (mounted && _mode == _refreshindicatormode.snap) { setstate(() { // show the indeterminate progress indicator. _mode = _refreshindicatormode.refresh; }); final future<void> refreshresult = widget.onrefresh(); refreshresult.whencomplete(() { if (mounted && _mode == _refreshindicatormode.refresh) { completer.complete(); _dismiss(_refreshindicatormode.done); } }); } }); } future<void> show({bool attop = true}) { if (_mode != _refreshindicatormode.refresh && _mode != _refreshindicatormode.snap) { if (_mode == null) { _start(attop ? axisdirection.down : axisdirection.up); } _show(); } return _pendingrefreshfuture; } @override widget build(buildcontext context) { // assert(debugcheckhasmateriallocalizations(context)); final widget child = notificationlistener<scrollnotification>( onnotification: _handlescrollnotification, child: notificationlistener<overscrollindicatornotification>( onnotification: _handleindicatornotification, child: widget.child, ), ); assert(() { if (_mode == null) { assert(_dragoffset == null); assert(_isindicatorattop == null); } else { assert(_dragoffset != null); assert(_isindicatorattop != null); } return true; }()); final bool showindeterminateindicator = _mode == _refreshindicatormode.refresh || _mode == _refreshindicatormode.done; return stack( children: <widget>[ // ============增加占位,源码600行左右================= nestedscrollview( headersliverbuilder: (context, innerboxisscrolled) { return [ slivertoboxadapter( child: animatedbuilder( animation: _positioncontroller, builder: (context, _) { // 50是我loading动画的高度,因此这里写死了 return sizedbox(height: 50 * _positioncontroller.value); }), ) ]; }, body: child, ), // ============增加占位================= if (_mode != null) positioned( top: _isindicatorattop! ? widget.edgeoffset : null, bottom: !_isindicatorattop! ? widget.edgeoffset : null, left: 0.0, right: 0.0, child: sizetransition( axisalignment: _isindicatorattop! ? 1.0 : -1.0, sizefactor: _positionfactor, // this is what brings it down // ============修改返回的loading样式================= child: container( alignment: _isindicatorattop! ? alignment.topcenter : alignment.bottomcenter, child: scaletransition( scale: _scalefactor, child: container( color: widget.color, width: 50, height: 50, child: const text("loading"), ), ), ), // ============修改返回的loading样式================= ), ), ], ); } }
2.1.3. 使用
refreshwidget( color: colors.blue, onrefresh: () async { await future.delayed(duration(seconds: 2)); }, child: center( child: singlechildscrollview( // 滚动区域的内容 // child: , ), ), );
3. 增加属性控制
根据上述的试验,我们优化一下,使下拉刷新组件更合理,新增以下两个属性:
keepscrolloffset
:自定义是否需要等待刷新完成后列表再回弹到顶部loadingwidget
:可以自定义loading样式,默认使用refreshindicator
的的loading
3.1. 难点与思路
难点:
- 占位元素的高度需要与用户传入的自定义
loading
的高度一致,如果写死的话,会导致类似这样的bug
思路:
- 占位
sizedbox
的child
设置为自定义的loading
,sizedbox
的高度不设置时,他的高度就是元素的高度 - 当处于正在刷新状态时,就将
sizedbox
的高度设置为null
遗留问题:
- 目前代码中写死了默认高度55(参照我完整代码的396行),如果传入的自定义
loading
高度大于55,松开时会有一点弹跳效果,暂时没有找到更好的解决方案,如果大家有更好的方案欢迎讨论一下
3.2. 完整代码
lib/widget/refresh_widget.dart
import 'dart:async'; import 'dart:math' as math; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart' show clampdouble; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; // =========修改下拉比例触发刷新,源码18行左右========= const double _kdragcontainerextentpercentage = 0.1; const double _kdragsizefactorlimit = 1; // =========修改下拉比例触发刷新========= const duration _kindicatorsnapduration = duration(milliseconds: 150); const duration _kindicatorscaleduration = duration(milliseconds: 200); typedef refreshcallback = future<void> function(); enum _refreshindicatormode { drag, // pointer is down. armed, // dragged far enough that an up event will run the onrefresh callback. snap, // animating to the indicator's final "displacement". refresh, // running the refresh callback. done, // animating the indicator's fade-out after refreshing. canceled, // animating the indicator's fade-out after not arming. } /// used to configure how [refreshindicator] can be triggered. enum refreshindicatortriggermode { anywhere, onedge, } enum _indicatortype { material, adaptive } // ======更改名字,源码119行左右====== class refreshwidget extends statefulwidget { const refreshwidget({ super.key, this.loadingwidget, this.keepscrolloffset = false, required this.child, this.displacement = 40.0, this.edgeoffset = 0.0, required this.onrefresh, this.color, this.backgroundcolor, this.notificationpredicate = defaultscrollnotificationpredicate, this.semanticslabel, this.semanticsvalue, this.strokewidth = refreshprogressindicator.defaultstrokewidth, this.triggermode = refreshindicatortriggermode.onedge, }) : _indicatortype = _indicatortype.material; const refreshwidget.adaptive({ super.key, this.loadingwidget, this.keepscrolloffset = false, required this.child, this.displacement = 40.0, this.edgeoffset = 0.0, required this.onrefresh, this.color, this.backgroundcolor, this.notificationpredicate = defaultscrollnotificationpredicate, this.semanticslabel, this.semanticsvalue, this.strokewidth = refreshprogressindicator.defaultstrokewidth, this.triggermode = refreshindicatortriggermode.onedge, }) : _indicatortype = _indicatortype.adaptive; // 自定义loading final widget? loadingwidget; // 刷新时是否保留顶部的偏移 final bool keepscrolloffset; final widget child; final double displacement; final double edgeoffset; final refreshcallback onrefresh; final color? color; final color? backgroundcolor; final scrollnotificationpredicate notificationpredicate; final string? semanticslabel; final string? semanticsvalue; final double strokewidth; final _indicatortype _indicatortype; final refreshindicatortriggermode triggermode; @override refreshwidgetstate createstate() => refreshwidgetstate(); } // 改名称,源码266行左右 class refreshwidgetstate extends state<refreshwidget> with tickerproviderstatemixin<refreshwidget> { late animationcontroller _positioncontroller; late animationcontroller _scalecontroller; late animation<double> _positionfactor; late animation<double> _scalefactor; late animation<double> _value; late animation<color?> _valuecolor; _refreshindicatormode? _mode; late future<void> _pendingrefreshfuture; bool? _isindicatorattop; double? _dragoffset; late color _effectivevaluecolor = widget.color ?? theme.of(context).colorscheme.primary; static final animatable<double> _threequartertween = tween<double>(begin: 0.0, end: 0.75); static final animatable<double> _kdragsizefactorlimittween = tween<double>(begin: 0.0, end: _kdragsizefactorlimit); static final animatable<double> _onetozerotween = tween<double>(begin: 1.0, end: 0.0); @override void initstate() { super.initstate(); _positioncontroller = animationcontroller(vsync: this); _positionfactor = _positioncontroller.drive(_kdragsizefactorlimittween); _value = _positioncontroller.drive( _threequartertween); // the "value" of the circular progress indicator during a drag. _scalecontroller = animationcontroller(vsync: this); _scalefactor = _scalecontroller.drive(_onetozerotween); } @override void didchangedependencies() { _setupcolortween(); super.didchangedependencies(); } @override void didupdatewidget(covariant refreshwidget oldwidget) { super.didupdatewidget(oldwidget); if (oldwidget.color != widget.color) { _setupcolortween(); } } @override void dispose() { _positioncontroller.dispose(); _scalecontroller.dispose(); super.dispose(); } void _setupcolortween() { // reset the current value color. _effectivevaluecolor = widget.color ?? theme.of(context).colorscheme.primary; final color color = _effectivevaluecolor; if (color.alpha == 0x00) { // set an always stopped animation instead of a driven tween. _valuecolor = alwaysstoppedanimation<color>(color); } else { // respect the alpha of the given color. _valuecolor = _positioncontroller.drive( colortween( begin: color.withalpha(0), end: color.withalpha(color.alpha), ).chain( curvetween( curve: const interval(0.0, 1.0 / _kdragsizefactorlimit), ), ), ); } } bool _shouldstart(scrollnotification notification) { return ((notification is scrollstartnotification && notification.dragdetails != null) || (notification is scrollupdatenotification && notification.dragdetails != null && widget.triggermode == refreshindicatortriggermode.anywhere)) && ((notification.metrics.axisdirection == axisdirection.up && notification.metrics.extentafter == 0.0) || (notification.metrics.axisdirection == axisdirection.down && notification.metrics.extentbefore == 0.0)) && _mode == null && _start(notification.metrics.axisdirection); } bool _handlescrollnotification(scrollnotification notification) { if (!widget.notificationpredicate(notification)) { return false; } if (_shouldstart(notification)) { setstate(() { _mode = _refreshindicatormode.drag; }); return false; } bool? indicatorattopnow; switch (notification.metrics.axisdirection) { case axisdirection.down: case axisdirection.up: indicatorattopnow = true; case axisdirection.left: case axisdirection.right: indicatorattopnow = null; } if (indicatorattopnow != _isindicatorattop) { if (_mode == _refreshindicatormode.drag || _mode == _refreshindicatormode.armed) { _dismiss(_refreshindicatormode.canceled); } } else if (notification is scrollupdatenotification) { if (_mode == _refreshindicatormode.drag || _mode == _refreshindicatormode.armed) { if ((notification.metrics.axisdirection == axisdirection.down && notification.metrics.extentbefore > 0.0) || (notification.metrics.axisdirection == axisdirection.up && notification.metrics.extentafter > 0.0)) { _dismiss(_refreshindicatormode.canceled); } else { if (notification.metrics.axisdirection == axisdirection.down) { _dragoffset = _dragoffset! - notification.scrolldelta!; } else if (notification.metrics.axisdirection == axisdirection.up) { _dragoffset = _dragoffset! + notification.scrolldelta!; } _checkdragoffset(notification.metrics.viewportdimension); } } if (_mode == _refreshindicatormode.armed && notification.dragdetails == null) { _show(); } } else if (notification is overscrollnotification) { if (_mode == _refreshindicatormode.drag || _mode == _refreshindicatormode.armed) { if (notification.metrics.axisdirection == axisdirection.down) { _dragoffset = _dragoffset! - notification.overscroll; } else if (notification.metrics.axisdirection == axisdirection.up) { _dragoffset = _dragoffset! + notification.overscroll; } _checkdragoffset(notification.metrics.viewportdimension); } } else if (notification is scrollendnotification) { switch (_mode) { case _refreshindicatormode.armed: _show(); case _refreshindicatormode.drag: _dismiss(_refreshindicatormode.canceled); case _refreshindicatormode.canceled: case _refreshindicatormode.done: case _refreshindicatormode.refresh: case _refreshindicatormode.snap: case null: // do nothing break; } } return false; } bool _handleindicatornotification( overscrollindicatornotification notification) { if (notification.depth != 0 || !notification.leading) { return false; } if (_mode == _refreshindicatormode.drag) { notification.disallowindicator(); return true; } return false; } bool _start(axisdirection direction) { assert(_mode == null); assert(_isindicatorattop == null); assert(_dragoffset == null); switch (direction) { case axisdirection.down: case axisdirection.up: _isindicatorattop = true; case axisdirection.left: case axisdirection.right: _isindicatorattop = null; return false; } _dragoffset = 0.0; _scalecontroller.value = 0.0; _positioncontroller.value = 0.0; return true; } void _checkdragoffset(double containerextent) { assert(_mode == _refreshindicatormode.drag || _mode == _refreshindicatormode.armed); double newvalue = _dragoffset! / (containerextent * _kdragcontainerextentpercentage); if (_mode == _refreshindicatormode.armed) { newvalue = math.max(newvalue, 1.0 / _kdragsizefactorlimit); } _positioncontroller.value = clampdouble(newvalue, 0.0, 1.0); // this triggers various rebuilds if (_mode == _refreshindicatormode.drag && _valuecolor.value!.alpha == _effectivevaluecolor.alpha) { _mode = _refreshindicatormode.armed; } } // stop showing the refresh indicator. future<void> _dismiss(_refreshindicatormode newmode) async { await future<void>.value(); assert(newmode == _refreshindicatormode.canceled || newmode == _refreshindicatormode.done); setstate(() { _mode = newmode; }); switch (_mode!) { // ===========刷新完成,需要将_positioncontroller置为0,源码498行左右========= case _refreshindicatormode.done: await future.wait([ _scalecontroller.animateto(1.0, duration: _kindicatorscaleduration), _positioncontroller.animateto(0.0, duration: _kindicatorscaleduration) ]); // ===========刷新完成,需要将_positioncontroller置为0========= case _refreshindicatormode.canceled: await _positioncontroller.animateto(0.0, duration: _kindicatorscaleduration); case _refreshindicatormode.armed: case _refreshindicatormode.drag: case _refreshindicatormode.refresh: case _refreshindicatormode.snap: assert(false); } if (mounted && _mode == newmode) { _dragoffset = null; _isindicatorattop = null; setstate(() { _mode = null; }); } } void _show() { assert(_mode != _refreshindicatormode.refresh); assert(_mode != _refreshindicatormode.snap); final completer<void> completer = completer<void>(); _pendingrefreshfuture = completer.future; _mode = _refreshindicatormode.snap; _positioncontroller .animateto(1.0 / _kdragsizefactorlimit, duration: _kindicatorsnapduration) .then<void>((void value) { if (mounted && _mode == _refreshindicatormode.snap) { setstate(() { // show the indeterminate progress indicator. _mode = _refreshindicatormode.refresh; }); final future<void> refreshresult = widget.onrefresh(); refreshresult.whencomplete(() { if (mounted && _mode == _refreshindicatormode.refresh) { completer.complete(); _dismiss(_refreshindicatormode.done); } }); } }); } future<void> show({bool attop = true}) { if (_mode != _refreshindicatormode.refresh && _mode != _refreshindicatormode.snap) { if (_mode == null) { _start(attop ? axisdirection.down : axisdirection.up); } _show(); } return _pendingrefreshfuture; } // 计算占位元素的高度 double? calcheight(double percent) { // 刷新时不保留占位 if (!widget.keepscrolloffset) return 0; // 55是默认loading动画的高度,如果传入的自定义loading高度大于55,松开时会有一点弹跳效果,暂时没有找到好的结局方案,如果你有好的解决方案,希望分享一下 if (widget.loadingwidget == null) { return 55 * percent; } if (_mode != _refreshindicatormode.refresh) { return 55 * percent; } return null; } @override widget build(buildcontext context) { // assert(debugcheckhasmateriallocalizations(context)); final widget child = notificationlistener<scrollnotification>( onnotification: _handlescrollnotification, child: notificationlistener<overscrollindicatornotification>( onnotification: _handleindicatornotification, child: widget.child, ), ); assert(() { if (_mode == null) { assert(_dragoffset == null); assert(_isindicatorattop == null); } else { assert(_dragoffset != null); assert(_isindicatorattop != null); } return true; }()); final bool showindeterminateindicator = _mode == _refreshindicatormode.refresh || _mode == _refreshindicatormode.done; return stack( children: <widget>[ // ============增加占位================= nestedscrollview( headersliverbuilder: (context, innerboxisscrolled) { return [ slivertoboxadapter( child: animatedbuilder( animation: _positioncontroller, builder: (context, _) { // 占位元素 return sizedbox( height: calcheight(_positioncontroller.value), child: opacity( opacity: 0, child: widget.loadingwidget, ), ); }), ) ]; }, body: child, ), if (_mode != null) positioned( top: _isindicatorattop! ? widget.edgeoffset : null, bottom: !_isindicatorattop! ? widget.edgeoffset : null, left: 0.0, right: 0.0, child: sizetransition( axisalignment: _isindicatorattop! ? 1.0 : -1.0, sizefactor: _positionfactor, // this is what brings it down child: container( alignment: _isindicatorattop! ? alignment.topcenter : alignment.bottomcenter, child: scaletransition( scale: _scalefactor, // ============自定loading或使用默认loading================= child: widget.loadingwidget ?? animatedbuilder( animation: _positioncontroller, builder: (buildcontext context, widget? child) { final widget materialindicator = refreshprogressindicator( semanticslabel: widget.semanticslabel ?? materiallocalizations.of(context) .refreshindicatorsemanticlabel, semanticsvalue: widget.semanticsvalue, value: showindeterminateindicator ? null : _value.value, valuecolor: _valuecolor, backgroundcolor: widget.backgroundcolor, strokewidth: widget.strokewidth, ); final widget cupertinoindicator = cupertinoactivityindicator( color: widget.color, ); switch (widget._indicatortype) { case _indicatortype.material: return materialindicator; case _indicatortype.adaptive: { final themedata theme = theme.of(context); switch (theme.platform) { case targetplatform.android: case targetplatform.fuchsia: case targetplatform.linux: case targetplatform.windows: return materialindicator; case targetplatform.ios: case targetplatform.macos: return cupertinoindicator; } } } }, ), ), ), ), ), ], ); } }
3.3. 使用
refreshwidget( keepscrolloffset: true, // 刷新时是否保留顶部偏移,默认不保留 loadingwidget: container( height: 30, width: 100, color: colors.amber, alignment: alignment.center, child: const text('正在加载...'), ), onrefresh: () async { await future.delayed(duration(seconds: 2)); }, child: center( child: singlechildscrollview( // 滚动区域的内容 // child: , ), ), );
3.4. 效果
以上就是flutter自定义下拉刷新时的loading样式的方法详解的详细内容,更多关于flutter自定义loading样式的资料请关注代码网其它相关文章!
发表评论