Flutter的SmartRefresher

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:滚动控制器

接下来,我们来看一下 SmartRefreshercreateState 方法,这个方法会创建 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组件,如SliverFillViewportSliverListSliverGrid等等,同时会加入下拉刷新头部和上拉加载更多底部组件。

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);
}


最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值