SmartRefresher 是一个可以自定义下拉刷新和上拉加载的 Flutter 组件,它继承自 StatefulWidget,并实现了 RefreshIndicator 接口。下面我们来看一下 SmartRefresher 的源代码实现。
首先,我们来看一下 SmartRefresher 的构造函数:
SmartRefresher({Key? key,
required this.controller,
this.child,
this.header,
this.footer,
this.enablePullDown: true,
this.enablePullUp: false,
this.enableTwoLevel: false,
this.onRefresh,
this.onLoading,
this.onTwoLevel,
this.dragStartBehavior,
this.primary,
this.cacheExtent,
this.semanticChildCount,
this.reverse,
this.physics,
this.scrollDirection,
this.scrollController})
: builder = null,
super(key: key);
可以看到,SmartRefresher 的构造函数接收很多参数,其中比较重要的是:
-
child:刷新内容的子组件 -
controller:刷新控制器,用于管理刷新状态、刷新数据等 -
enablePullDown:是否允许下拉刷新,默认为 true -
enablePullUp:是否允许上拉加载,默认为 false -
header:下拉刷新的头部组件,默认为TargetPlatform.iOS ? ClassicHeader() : MaterialClassicHeader() -
footer:上拉加载的底部组件,默认为ClassicFooter() -
onRefresh:下拉刷新回调函数 -
onLoading:上拉加载回调函数 -
physics:滚动物理引擎 -
scrollDirection:滚动方向 -
shrinkWrap:是否按内容大小缩小 -
anchor:锚点值,用于确定首次滚动位置 -
scrollController:滚动控制器
接下来,我们来看一下 SmartRefresher 的 createState 方法,这个方法会创建 SmartRefresherState 对象,用于管理 SmartRefresher 的状态:
@override
SmartRefresherState createState() => SmartRefresherState();
SmartRefresherState 的源代码实现非常复杂,我们简单地看一下其主要的方法和属性。
首先,我们来看一下 SmartRefresherState 的属性:
RefreshPhysics? _physics;
bool _updatePhysics = false;
double viewportExtent = 0;
bool _canDrag = true;
我们在看下她initState方法
@override
void initState() {
// 这里如果controller.initialRefresh=true则会设置一个帧回调方法,
if (widget.controller.initialRefresh) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
// 帧结束时调用requestRefresh,requestRefresh回调用controller.onRefresh()方法
if (mounted) widget.controller.requestRefresh();
});
}
widget.controller._bindState(this);
super.initState();
}
其build方法,
@override
Widget build(BuildContext context) {
final RefreshConfiguration? configuration =
RefreshConfiguration.of(context);
Widget? body;
if (widget.builder != null)
body = widget.builder!(
context,
_getScrollPhysics(configuration, AlwaysScrollableScrollPhysics())
as RefreshPhysics);
else {
List<Widget>? slivers =
_buildSliversByChild(context, widget.child, configuration);
body = _buildBodyBySlivers(widget.child, slivers, configuration);
}
if (configuration == null) {
body = RefreshConfiguration(child: body!);
}
/// `LayoutBuilder`是一个能够构建有关父级组件约束的Widget。
/// `biggest`属性表示父级组件最大可用空间
/// 将`cons.biggest.height`赋值给了`viewportExtent`,取到`LayoutBuilder`在父级中可用的最大高度
/// 同时`body`作为子组件,使用这个高度进行自适应布局
return LayoutBuilder(
builder: (c2, cons) {
viewportExtent = cons.biggest.height;
return body!;
},
);
}
_buildSliversByChild 和 _buildBodyBySlivers这两个方法都是用于根据child参数构建可滚动的子组件列表的方法。
具体来说,_buildSliversByChild方法会将child参数转换为一个可滚动的组件列表,以便在SmartRefresher中使用,该方法内部会递归处理所有子组件,并根据不同类型的组件构建不同的Sliver组件,如SliverFillViewport、SliverList、SliverGrid等等,同时会加入下拉刷新头部和上拉加载更多底部组件。
if (widget.enablePullDown || widget.enableTwoLevel) {
slivers?.insert(
0,
widget.header ??
(configuration?.headerBuilder != null
? configuration?.headerBuilder!()
: null) ??
defaultHeader);
}
//insert header or footer
if (widget.enablePullUp) {
slivers?.add(widget.footer ??
(configuration?.footerBuilder != null
? configuration?.footerBuilder!()
: null) ??
defaultFooter);
}
而_buildBodyBySlivers方法则是将上述可滚动的子组件列表与child参数组合在一起,构建一个包含所有子组件的整体滚动容器。
下拉刷新
无论是ClassicHeader还是MaterialClassicHeader,都是继承RefreshIndicator,因此我们只要看RefreshIndicator的代码
/// a widget implements ios pull down refresh effect and Android material RefreshIndicator overScroll effect
abstract class RefreshIndicator extends StatefulWidget {
/// refresh display style
final RefreshStyle? refreshStyle;
/// the visual extent indicator
final double height;
//layout offset
final double offset;
/// the stopped time when refresh complete or fail
final Duration completeDuration;
const RefreshIndicator(
{Key? key,
this.height: 60.0,
this.offset: 0.0,
this.completeDuration: const Duration(milliseconds: 500),
this.refreshStyle: RefreshStyle.Follow})
: super(key: key);
}
是一个StatefulWidget组件,对应的State组件是RefreshIndicatorState:
实现了RefreshProcessor接口,该类是 SmartRefresher 中头部和底部刷新指示器的接口。
该接口有以下方法:
-
onOffsetChange(double offset):当刷新指示器移动到视图边缘时调用,返回偏移量offset。 -
onModeChange(RefreshStatus? mode):当刷新模式发生变化时调用,传递当前的刷新状态mode。 -
readyToRefresh():当刷新指示器准备好进行刷新时调用,等待此函数完成后回调onRefresh。 -
endRefresh():当刷新指示器完成刷新后调用。 -
resetValue():当刷新指示器的值被重置时调用,以便下次使用。
开发者可以使用该接口实现自己的头部和底部刷新指示器,并将其传递给 SmartRefresher 组件,以自定义刷新样式。
它还实现了IndicatorStateMixin
一个与 SmartRefresher 相关的状态监听器,可以监听滚动的位置和滚动状态,并响应相应的操作。
/// mixin in IndicatorState,it will get position and remove when dispose,init mode state
///
/// help to finish the work that the header indicator and footer indicator need to do
mixin IndicatorStateMixin<T extends StatefulWidget, V> on State<T> {
SmartRefresher? refresher;
RefreshConfiguration? configuration;
SmartRefresherState? refresherState;
bool _floating = false;
set floating(floating) => _floating = floating;
get floating => _floating;
set mode(mode) => _mode?.value = mode;
get mode => _mode?.value;
RefreshNotifier<V?>? _mode;
ScrollActivity? get activity => _position!.activity;
// it doesn't support get the ScrollController as the listener, because it will cause "multiple scrollview use one ScrollController"
// error,only replace the ScrollPosition to listen the offset
ScrollPosition? _position;
// update ui
void update() {
if (mounted) setState(() {});
}
void _handleOffsetChange() {
if (!mounted) {
return;
}
final double overscrollPast = _calculateScrollOffset();
if (overscrollPast < 0.0) {
return;
}
_dispatchModeByOffset(overscrollPast);
}
void disposeListener() {
_mode?.removeListener(_handleModeChange);
_position?.removeListener(_handleOffsetChange);
_position = null;
_mode = null;
}
void _updateListener() {
configuration = RefreshConfiguration.of(context);
refresher = SmartRefresher.of(context);
refresherState = SmartRefresher.ofState(context);
RefreshNotifier<V>? newMode = V == RefreshStatus
? refresher!.controller.headerMode as RefreshNotifier<V>?
: refresher!.controller.footerMode as RefreshNotifier<V>?;
final ScrollPosition newPosition = Scrollable.of(context)!.position;
if (newMode != _mode) {
_mode?.removeListener(_handleModeChange);
_mode = newMode;
_mode?.addListener(_handleModeChange);
}
if (newPosition != _position) {
_position?.removeListener(_handleOffsetChange);
_onPositionUpdated(newPosition);
_position = newPosition;
_position?.addListener(_handleOffsetChange);
}
}
@override
void initState() {
// TODO: implement initState
if (V == RefreshStatus) {
SmartRefresher.of(context)?.controller.headerMode?.value =
RefreshStatus.idle;
}
super.initState();
}
@override
void dispose() {
// TODO: implement dispose
//1.3.7: here need to careful after add asSliver builder
disposeListener();
super.dispose();
}
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
_updateListener();
super.didChangeDependencies();
}
@override
void didUpdateWidget(T oldWidget) {
// TODO: implement didUpdateWidget
// needn't to update _headerMode,because it's state will never change
// 1.3.7: here need to careful after add asSliver builder
_updateListener();
super.didUpdateWidget(oldWidget);
}
void _onPositionUpdated(ScrollPosition newPosition) {
refresher!.controller.onPositionUpdated(newPosition);
}
void _handleModeChange();
double _calculateScrollOffset();
void _dispatchModeByOffset(double offset);
Widget buildContent(BuildContext context, V mode);
}