前言
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样式的资料请关注代码网其它相关文章!
发表评论