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);
}
网友评论