当前位置: 代码网 > it编程>App开发>Android > Flutter自定义下拉刷新时的loading样式的方法详解

Flutter自定义下拉刷新时的loading样式的方法详解

2024年05月18日 Android 我要评论
前言flutter中的下拉刷新,我们通常refreshindicator,可以通过backgroundcolor,color或strokewidth设置下拉刷新的颜色粗细等样式,但如果要自定义自己的w

前言

flutter中的下拉刷新,我们通常refreshindicator,可以通过backgroundcolorcolorstrokewidth设置下拉刷新的颜色粗细等样式,但如果要自定义自己的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主要是:refreshprogressindicatorcupertinoactivityindicator两种。

.../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

思路:

  • 占位sizedboxchild设置为自定义的loadingsizedbox的高度不设置时,他的高度就是元素的高度
  • 当处于正在刷新状态时,就将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样式的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com