我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎么使用Flutter刷新组件RefreshIndicator自定义样式demo

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎么使用Flutter刷新组件RefreshIndicator自定义样式demo

这篇文章主要介绍了怎么使用Flutter刷新组件RefreshIndicator自定义样式demo的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么使用Flutter刷新组件RefreshIndicator自定义样式demo文章都会有所收获,下面我们一起来看看吧。

前言

RefreshIndicator是Flutter里常见的下拉刷新组件,使用是比较方便的。但由于产品兄弟对其固定的刷新样式很是不满,而且代码中已经引入了很多RefreshIndicator,直接替换其他组件的话,对代码的改动可能比较大,所以只能自己动手改一改源码,在达到产品的要求的同时尽可能减少代码的修改。

简单的样式修改

简单的样式修改,如想换成顺时针旋转的 iOS 风格活动指示器,只需替换对应样式代码即可。查看RefreshIndicator的源码,代码翻到最下面就可以看到其实是自定义了一个RefreshProgressIndicator样式,通过继承CircularProgressIndicator来实现初始样式。

怎么使用Flutter刷新组件RefreshIndicator自定义样式demo

所以我们只需简单的替换掉该样式即可实现简单的样式修改。

AnimatedBuilder(  animation: _positionController,  builder: (BuildContext context, Widget? child) {    return ClipOval(      child: Container(          padding: const EdgeInsets.all(10),          decoration: BoxDecoration(              color: widget.backgroundColor ?? Colors.white),          child: CupertinoActivityIndicator(              color: widget.color)),    );  },)

如此便可实现简单的样式修改。

复杂的样式修改

简单的样式修改只是换换样式,对刷新动作本身是没有任何修改的,也就是刷新操作样式本身没有变,只是换了个皮。而国内的刷新操作样式基本是上图效果3,所以如果要在RefreshIndicator上修改成效果3,除了要将原有样式Stack改为Column外,还需要自己处理手势,这里可以使用Listener来操作手势。

代码如下,修改的地方都有注释。

// Copyright 2014 The Flutter Authors. All rights reserved.// Use of this source code is governed by a BSD-style license that can be// found in the LICENSE file.import 'dart:async';import 'dart:math' as math;import 'dart:ui';import 'package:flutter/cupertino.dart';import 'package:flutter/material.dart';import 'package:get/get.dart';// The over-scroll distance that moves the indicator to its maximum// displacement, as a percentage of the scrollable's container extent.const double _kDragContainerExtentPercentage = 0.25;// How much the scroll's drag gesture can overshoot the RefreshIndicator's// displacement; max displacement = _kDragSizeFactorLimit * displacement.const double _kDragSizeFactorLimit = 1.5;// When the scroll ends, the duration of the refresh indicator's animation// to the RefreshIndicator's displacement.const Duration _kIndicatorSnapDuration = Duration(milliseconds: 150);// The duration of the ScaleTransition that starts when the refresh action// has completed.const Duration _kIndicatorScaleDuration = Duration(milliseconds: 200);/// The signature for a function that's called when the user has dragged a/// [RefreshIndicator] far enough to demonstrate that they want the app to/// refresh. The returned [Future] must complete when the refresh operation is/// finished.////// Used by [RefreshIndicator.onRefresh].typedef RefreshCallback = Future<void> Function();// The state machine moves through these modes only when the scrollable// identified by scrollableKey has been scrolled to its min or max limit.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 {  /// The indicator can be triggered regardless of the scroll position  /// of the [Scrollable] when the drag starts.  anywhere,  /// The indicator can only be triggered if the [Scrollable] is at the edge  /// when the drag starts.  onEdge,}/// A widget that supports the Material "swipe to refresh" idiom.////// {@youtube 560 315 https://www.youtube.com/watch?v=ORApMlzwMdM}////// When the child's [Scrollable] descendant overscrolls, an animated circular/// progress indicator is faded into view. When the scroll ends, if the/// indicator has been dragged far enough for it to become completely opaque,/// the [onRefresh] callback is called. The callback is expected to update the/// scrollable's contents and then complete the [Future] it returns. The refresh/// indicator disappears after the callback's [Future] has completed.////// The trigger mode is configured by [RefreshIndicator.triggerMode].////// {@tool dartpad}/// This example shows how [RefreshIndicator] can be triggered in different ways.////// ** See code in examples/api/lib/material/refresh_indicator/refresh_indicator.0.dart **/// {@end-tool}////// ## Troubleshooting////// ### Refresh indicator does not show up////// The [RefreshIndicator] will appear if its scrollable descendant can be/// overscrolled, i.e. if the scrollable's content is bigger than its viewport./// To ensure that the [RefreshIndicator] will always appear, even if the/// scrollable's content fits within its viewport, set the scrollable's/// [Scrollable.physics] property to [AlwaysScrollableScrollPhysics]:////// ```dart/// ListView(///   physics: const AlwaysScrollableScrollPhysics(),///   children: .../// )/// ```////// A [RefreshIndicator] can only be used with a vertical scroll view.////// See also://////  * <https://material.io/design/platform-guidance/android-swipe-to-refresh.html>///  * [RefreshIndicatorState], can be used to programmatically show the refresh indicator.///  * [RefreshProgressIndicator], widget used by [RefreshIndicator] to show///    the inner circular progress spinner during refreshes.///  * [CupertinoSliverRefreshControl], an iOS equivalent of the pull-to-refresh pattern.///    Must be used as a sliver inside a [CustomScrollView] instead of wrapping///    around a [ScrollView] because it's a part of the scrollable instead of///    being overlaid on top of it.class RefreshIndicatorNeo extends StatefulWidget {  /// Creates a refresh indicator.  ///  /// The [onRefresh], [child], and [notificationPredicate] arguments must be  /// non-null. The default  /// [displacement] is 40.0 logical pixels.  ///  /// The [semanticsLabel] is used to specify an accessibility label for this widget.  /// If it is null, it will be defaulted to [MaterialLocalizations.refreshIndicatorSemanticLabel].  /// An empty string may be passed to avoid having anything read by screen reading software.  /// The [semanticsValue] may be used to specify progress on the widget.  const RefreshIndicatorNeo({    Key? 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,  })  : assert(child != null),        assert(onRefresh != null),        assert(notificationPredicate != null),        assert(strokeWidth != null),        assert(triggerMode != null),        super(key: key);  /// The widget below this widget in the tree.  ///  /// The refresh indicator will be stacked on top of this child. The indicator  /// will appear when child's Scrollable descendant is over-scrolled.  ///  /// Typically a [ListView] or [CustomScrollView].  final Widget child;  /// The distance from the child's top or bottom [edgeOffset] where  /// the refresh indicator will settle. During the drag that exposes the refresh  /// indicator, its actual displacement may significantly exceed this value.  ///  /// In most cases, [displacement] distance starts counting from the parent's  /// edges. However, if [edgeOffset] is larger than zero then the [displacement]  /// value is calculated from that offset instead of the parent's edge.  final double displacement;  /// The offset where [RefreshProgressIndicator] starts to appear on drag start.  ///  /// Depending whether the indicator is showing on the top or bottom, the value  /// of this variable controls how far from the parent's edge the progress  /// indicator starts to appear. This may come in handy when, for example, the  /// UI contains a top [Widget] which covers the parent's edge where the progress  /// indicator would otherwise appear.  ///  /// By default, the edge offset is set to 0.  ///  /// See also:  ///  ///  * [displacement], can be used to change the distance from the edge that  ///    the indicator settles.  final double edgeOffset;  /// A function that's called when the user has dragged the refresh indicator  /// far enough to demonstrate that they want the app to refresh. The returned  /// [Future] must complete when the refresh operation is finished.  final RefreshCallback onRefresh;  /// The progress indicator's foreground color. The current theme's  /// [ColorScheme.primary] by default.  final Color? color;  /// The progress indicator's background color. The current theme's  /// [ThemeData.canvasColor] by default.  final Color? backgroundColor;  /// A check that specifies whether a [ScrollNotification] should be  /// handled by this widget.  ///  /// By default, checks whether `notification.depth == 0`. Set it to something  /// else for more complicated layouts.  final ScrollNotificationPredicate notificationPredicate;  /// {@macro flutter.progress_indicator.ProgressIndicator.semanticsLabel}  ///  /// This will be defaulted to [MaterialLocalizations.refreshIndicatorSemanticLabel]  /// if it is null.  final String? semanticsLabel;  /// {@macro flutter.progress_indicator.ProgressIndicator.semanticsValue}  final String? semanticsValue;  /// Defines `strokeWidth` for `RefreshIndicator`.  ///  /// By default, the value of `strokeWidth` is 2.0 pixels.  final double strokeWidth;  /// Defines how this [RefreshIndicator] can be triggered when users overscroll.  ///  /// The [RefreshIndicator] can be pulled out in two cases,  /// 1, Keep dragging if the scrollable widget at the edge with zero scroll position  ///    when the drag starts.  /// 2, Keep dragging after overscroll occurs if the scrollable widget has  ///    a non-zero scroll position when the drag starts.  ///  /// If this is [RefreshIndicatorTriggerMode.anywhere], both of the cases above can be triggered.  ///  /// If this is [RefreshIndicatorTriggerMode.onEdge], only case 1 can be triggered.  ///  /// Defaults to [RefreshIndicatorTriggerMode.onEdge].  final RefreshIndicatorTriggerMode triggerMode;  @override  RefreshIndicatorNeoState createState() => RefreshIndicatorNeoState();}/// Contains the state for a [RefreshIndicator]. This class can be used to/// programmatically show the refresh indicator, see the [show] method.class RefreshIndicatorNeoState extends State<RefreshIndicatorNeo>    with TickerProviderStateMixin<RefreshIndicatorNeo> {  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;  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() {    final ThemeData theme = Theme.of(context);    _valueColor = _positionController.drive(      ColorTween(        begin: (widget.color ?? theme.colorScheme.primary).withOpacity(0.0),        end: (widget.color ?? theme.colorScheme.primary).withOpacity(1.0),      ).chain(CurveTween(        curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),      )),    );    super.didChangeDependencies();  }  @override  void didUpdateWidget(covariant RefreshIndicatorNeo oldWidget) {    super.didUpdateWidget(oldWidget);    if (oldWidget.color != widget.color) {      final ThemeData theme = Theme.of(context);      _valueColor = _positionController.drive(        ColorTween(          begin: (widget.color ?? theme.colorScheme.primary).withOpacity(0.0),          end: (widget.color ?? theme.colorScheme.primary).withOpacity(1.0),        ).chain(CurveTween(          curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),        )),      );    }  }  @override  void dispose() {    _positionController.dispose();    _scaleController.dispose();    super.dispose();  }  bool _shouldStart(ScrollNotification notification) {    // If the notification.dragDetails is null, this scroll is not triggered by    // user dragging. It may be a result of ScrollController.jumpTo or ballistic scroll.    // In this case, we don't want to trigger the refresh indicator.    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;        break;      case AxisDirection.left:      case AxisDirection.right:        indicatorAtTopNow = true;        break;    }    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) {        // On iOS start the refresh when the Scrollable bounces back from the        // overscroll (ScrollNotification indicating this don't have dragDetails        // because the scroll activity is not directly triggered by a drag).        _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,            needIntercept: true);      }    } else if (notification is ScrollEndNotification) {      switch (_mode) {        case _RefreshIndicatorMode.armed:          _show();          break;        case _RefreshIndicatorMode.drag:          _dismiss(_RefreshIndicatorMode.canceled);          break;        case _RefreshIndicatorMode.canceled:        case _RefreshIndicatorMode.done:        case _RefreshIndicatorMode.refresh:        case _RefreshIndicatorMode.snap:        case null:          // do nothing          break;      }    }    return false;  }  bool _handleGlowNotification(OverscrollIndicatorNotification notification) {    if (notification.depth != 0 || !notification.leading) return false;    if (_mode == _RefreshIndicatorMode.drag) {      notification.disallowGlow();      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;        break;      case AxisDirection.left:      case AxisDirection.right:        _isIndicatorAtTop = null;        // we do not support horizontal scroll views.        return false;    }    _dragOffset = 0.0;    _scaleController.value = 0.0;    _positionController.value = 0.0;    return true;  }  void _checkDragOffset(double containerExtent, {bool needIntercept = true}) {    if (needIntercept) {      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 =        newValue.clamp(0.0, 1.0); // this triggers various rebuilds    if (_mode == _RefreshIndicatorMode.drag &&        _valueColor.value!.alpha == 0xFF) {      _mode = _RefreshIndicatorMode.armed;    }  }  // Stop showing the refresh indicator.  Future<void> _dismiss(_RefreshIndicatorMode newMode, {Duration? time}) async {    await Future<void>.value();    // This can only be called from _show() when refreshing and    // _handleScrollNotification in response to a ScrollEndNotification or    // direction change.    assert(newMode == _RefreshIndicatorMode.canceled ||        newMode == _RefreshIndicatorMode.done);    setState(() {      _mode = newMode;    });    switch (_mode!) {      // 注释:刷新结束,关闭动画      case _RefreshIndicatorMode.done:        _scaleController            .animateTo(1.0, duration: time ?? _kIndicatorScaleDuration)            .whenComplete(() {});        _doneAnimation = Tween<double>(begin: getPos(pos.value), end: 0)            .animate(_scaleController);        if (_doneAnimation != null) {          _doneAnimation?.addListener(() {            //赋值高度            pos(_doneAnimation?.value ?? 0);            if ((_doneAnimation?.value ?? 0) == 0) {              _doneAnimation = null;            }          });        }        break;      case _RefreshIndicatorMode.canceled:        await _positionController.animateTo(0.0,            duration: time ?? _kIndicatorScaleDuration);        break;      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) {        assert(widget.onRefresh != null);        setState(() {          // Show the indeterminate progress indicator.          _mode = _RefreshIndicatorMode.refresh;        });        // 注释:删掉这段代码,因为需要跟随手势,在手势释放的时候才执行,见下方手势控制onPointerUp        // final Future<void> refreshResult = widget.onRefresh();        // assert(() {        //   if (refreshResult == null)        //     FlutterError.reportError(FlutterErrorDetails(        //       exception: FlutterError(        //         'The onRefresh callback returned null.\n'        //         'The RefreshIndicator onRefresh callback must return a Future.',        //       ),        //       context: ErrorDescription('when calling onRefresh'),        //       library: 'material library',        //     ));        //   return true;        // }());        // if (refreshResult == null) return;        // refreshResult.whenComplete(() {        //   if (mounted && _mode == _RefreshIndicatorMode.refresh) {        //     completer.complete();        //     _dismiss(_RefreshIndicatorMode.done);        //   }        // });      }    });  }  /// Show the refresh indicator and run the refresh callback as if it had  /// been started interactively. If this method is called while the refresh  /// callback is running, it quietly does nothing.  ///  /// Creating the [RefreshIndicator] with a [GlobalKey<RefreshIndicatorState>]  /// makes it possible to refer to the [RefreshIndicatorState].  ///  /// The future returned from this method completes when the  /// [RefreshIndicator.onRefresh] callback's future completes.  ///  /// If you await the future returned by this function from a [State], you  /// should check that the state is still [mounted] before calling [setState].  ///  /// When initiated in this manner, the refresh indicator is independent of any  /// actual scroll view. It defaults to showing the indicator at the top. To  /// show it at the bottom, set `atTop` to false.  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;  }  //点击时的Y  double _downY = 0.0;  //最后的移动Y  double _lastMoveY = 0.0;  //手势移动距离,对应下拉效果的位移  //因为需要制造弹性效果,调用getPos()模拟弹性  RxDouble pos = 0.0.obs;  //手势状态  MoveType moveType = MoveType.UP;  final double bottomImg = 10;  //手势下拉动画,主要对pos赋值  late Animation<double>? _animation;  //结束动画,主要对pos重新赋值至0  late Animation<double>? _doneAnimation;  late AnimationController _controller;  ///模拟下拉的弹性  double getPos(double pos) {    if (pos <= 0) {      return 0;    } else if (pos < 100) {      return pos * 0.7;    } else if (pos < 200) {      return 70 + ((pos - 100) * 0.5);    } else if (pos < 300) {      return 120 + ((pos - 200) * 0.3);    } else {      return 150 + ((pos - 300) * 0.1);    }  }  @override  Widget build(BuildContext context) {    assert(debugCheckHasMaterialLocalizations(context));    final Widget child = NotificationListener<ScrollNotification>(      onNotification: _handleScrollNotification,      child: widget.child,      // NotificationListener<OverscrollIndicatorNotification>(      //   // onNotification: _handleGlowNotification,      //   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;    double imgHeight = MediaQueryData.fromWindow(window).size.width / 7;    double imgAllHeight = imgHeight + bottomImg;    return Listener(        onPointerDown: (PointerDownEvent event) {          //手指按下的距离          _downY = event.position.distance;          moveType = MoveType.DOWN;        },        onPointerMove: (PointerMoveEvent event) {          if (moveType != MoveType.MOVE || _mode == null) {            setState(() {              moveType = MoveType.MOVE;            });          }          moveType = MoveType.MOVE;          //手指移动的距离          var position = event.position.distance;          //判断距离差          var detal = position - _lastMoveY;          ///到达顶部才计算          if (_isIndicatorAtTop != null &&              _isIndicatorAtTop! &&              _mode != null) {            pos(position - _downY);            if (detal > 0) {              //================向下移动================            } else {              //================向上移动================              ///当刷新动画执行时,手指上滑就直接取消刷新动画              if (_mode == _RefreshIndicatorMode.refresh && pos.value != 0) {                _dismiss(_RefreshIndicatorMode.canceled,                    time: Duration(microseconds: 500));              }            }          }          _lastMoveY = position;        },        onPointerUp: (PointerUpEvent event) {          if (_isIndicatorAtTop != null && _isIndicatorAtTop!) {            double heightPos = pos.value;            double imgHeight = 0;            ///计算图片高度,因为最终转成pos,因为pos被转换过getPos()            //所以反转的时候需要再次计算            if (imgAllHeight < 100) {              imgHeight = imgAllHeight / 0.7;            } else if (imgAllHeight < 200) {              imgHeight = (imgAllHeight - 20) / 0.5;            } else if (imgAllHeight < 300) {              imgHeight = (imgAllHeight - 60) / 0.3;            }            //松手后的回弹效果            _controller = AnimationController(              vsync: this,              duration: Duration(milliseconds: 250),            )..forward().whenComplete(() {                ///动画结束后触发onRefresh()方法                if (_mode == _RefreshIndicatorMode.refresh) {                  final Completer<void> completer = Completer<void>();                  _pendingRefreshFuture = completer.future;                  final Future<void> refreshResult = widget.onRefresh();                  assert(() {                    if (refreshResult == null) {                      FlutterError.reportError(FlutterErrorDetails(                        exception: FlutterError(                          'The onRefresh callback returned null.\n'                          'The RefreshIndicator onRefresh callback must return a Future.',                        ),                        context: ErrorDescription('when calling onRefresh'),                        library: 'material library',                      ));                    }                    return true;                  }());                  if (refreshResult == null) return;                  refreshResult.whenComplete(() {                    if (mounted && _mode == _RefreshIndicatorMode.refresh) {                      completer.complete();                      ///onRefresh()执行完后关闭动画                      _dismiss(_RefreshIndicatorMode.done);                    }                  });                }              });            _animation = Tween<double>(begin: heightPos, end: imgHeight)                .animate(_controller);            _animation?.addListener(() {              //下拉动画变化,赋值高度              if (_mode == _RefreshIndicatorMode.refresh) {                pos(_animation?.value ?? 0);                if (_animation?.value == imgHeight) {                  _animation = null;                }              }            });          }          moveType = MoveType.UP;        },        child: Obx(() => Column(              children: [                if (_isIndicatorAtTop != null &&                        _isIndicatorAtTop! &&                        _mode != null &&                        moveType == MoveType.MOVE ||                    pos.value != 0)                  ScaleTransition(                    scale: _scaleFactor,                    child: AnimatedBuilder(                      animation: _positionController,                      builder: (BuildContext context, Widget? child) {                        //使用gif动画                        return Obx(() => Container(                              height: getPos(pos.value),                              alignment: Alignment.bottomCenter,                              child: Container(                                padding: EdgeInsets.only(bottom: bottomImg),                                child: Image.asset(                                  "assets/gif_load.gif",                                  width: imgHeight * 2,                                  height: imgHeight,                                ),                              ),                            ));                      },                    ),                  ),                Expanded(child: child),              ],            )));  }}enum MoveType {  DOWN,  MOVE,  UP,}

代码如上,其中还额外使用了GetX来控制手势位移距离,然后再将末尾的assets/gif_load.gif更换为各自需要的gif资源即可。

关于“怎么使用Flutter刷新组件RefreshIndicator自定义样式demo”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“怎么使用Flutter刷新组件RefreshIndicator自定义样式demo”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

怎么使用Flutter刷新组件RefreshIndicator自定义样式demo

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Flutter刷新组件RefreshIndicator自定义样式demo

这篇文章主要介绍了Flutter刷新组件RefreshIndicator自定义样式demo,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-22

怎么使用Flutter刷新组件RefreshIndicator自定义样式demo

这篇文章主要介绍了怎么使用Flutter刷新组件RefreshIndicator自定义样式demo的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么使用Flutter刷新组件RefreshIndicator自定
2023-07-05

vue怎么自定义和使用开关组件

这篇“vue怎么自定义和使用开关组件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue怎么自定义和使用开关组件”文章吧。s
2023-06-29

Flutter怎么利用Hero组件实现自定义路径动画效果

这篇“Flutter怎么利用Hero组件实现自定义路径动画效果”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Flutter怎
2023-06-30

vue自定义组件怎么添加使用原生事件

今天小编给大家分享一下vue自定义组件怎么添加使用原生事件的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。自定义组件如何添加使
2023-06-30

微信小程序怎么自定义组件Component使用

本篇内容介绍了“微信小程序怎么自定义组件Component使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1- 前言在本文中你将收获小程序
2023-07-05

微信小程序怎么使用自定义组件封装原生image组件

本文小编为大家详细介绍“微信小程序怎么使用自定义组件封装原生image组件 ”,内容详细,步骤清晰,细节处理妥当,希望这篇“微信小程序怎么使用自定义组件封装原生image组件 ”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习
2023-06-26

Vue组件的自定义事件和全局事件总线怎么使用

这篇“Vue组件的自定义事件和全局事件总线怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Vue组件的自定义事件和全局
2023-07-05

PyQt5中怎么使用样式表来自定义应用程序的外观

在PyQt5中,可以使用样式表来自定义应用程序的外观。样式表是一种类似于CSS的语法,可以用来改变应用程序的窗口、按钮、标签等控件的外观和布局。以下是一个简单的示例,演示如何在PyQt5中使用样式表来自定义应用程序的外观:import
PyQt5中怎么使用样式表来自定义应用程序的外观
2024-03-12

详解怎么使用vue封装一个自定义日历组件

怎么开发一个自定义日历的vue组件,下面本篇文章就手把手教你如何封装一个自定义日历组件,希望对大家有所帮助!
2023-05-14

vue怎么使用脚手架vue-cli创建并引入自定义组件

小编给大家分享一下vue怎么使用脚手架vue-cli创建并引入自定义组件,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!一、创建并引入一个组件1、创建组件vue-cli中的所有组件都是存放在components文件夹下面的,
2023-06-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录