xuelongqy / flutter_easy_refresh

A flutter widget that provides pull-down refresh and pull-up load.
https://xuelongqy.github.io/flutter_easy_refresh/
MIT License
3.89k stars 633 forks source link

AnimationController : invalid overscroll value #583

Open dehypnosis opened 2 years ago

dehypnosis commented 2 years ago

Thank you for the awesome work. I found an issue in latest version 3.0.4

The indicator is being bouncing up and down when i try to scroll while refreshing is ongoing.

══╡ EXCEPTION CAUGHT BY ANIMATION LIBRARY
╞═════════════════════════════════════════════════════════
The following assertion was thrown while notifying listeners for
AnimationController:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.
The method was called to consider a change from 0.0 to -7.00854,
which is a delta of -7.0 units.
However, it returned an overscroll of 57.0 units, which has a
greater magnitude than the delta. The
applyBoundaryConditions method is only supposed to reduce the
possible range of movement, not
increase it.
The scroll extents are 0.0 .. 1145.0, and the viewport dimension is
679.0.

When the exception was thrown, this was the stack:
#0      ScrollPosition.applyBoundaryConditions.<anonymous closure>
(package:flutter/src/widgets/scroll_position.dart:481:9)
#1      ScrollPosition.applyBoundaryConditions
(package:flutter/src/widgets/scroll_position.dart:493:6)
#2      ScrollPosition.setPixels
(package:flutter/src/widgets/scroll_position.dart:262:33)
#3      ScrollPositionWithSingleContext.setPixels
(package:flutter/src/widgets/scroll_position_with_single_context.da
rt:82:18)
#4      DrivenScrollActivity._tick
(package:flutter/src/widgets/scroll_activity.dart:648:18)
#5      AnimationLocalListenersMixin.notifyListeners
(package:flutter/src/animation/listener_helpers.dart:155:19)
#6      AnimationController._tick
(package:flutter/src/animation/animation_controller.dart:830:5)
#7      Ticker._tick
(package:flutter/src/scheduler/ticker.dart:238:12)
#8      SchedulerBinding._invokeFrameCallback
(package:flutter/src/scheduler/binding.dart:1146:15)
#9      SchedulerBinding.handleBeginFrame.<anonymous closure>
(package:flutter/src/scheduler/binding.dart:1059:11)
#10     _LinkedHashMapMixin.forEach
(dart:collection-patch/compact_hash.dart:614:13)
#11     SchedulerBinding.handleBeginFrame
(package:flutter/src/scheduler/binding.dart:1057:17)
#12     SchedulerBinding._handleBeginFrame
(package:flutter/src/scheduler/binding.dart:976:5)
#16     _invoke1 (dart:ui/hooks.dart:170:10)
#17     PlatformDispatcher._beginFrame
(dart:ui/platform_dispatcher.dart:286:5)
#18     _beginFrame (dart:ui/hooks.dart:104:31)
(elided 3 frames from dart:async)

The AnimationController notifying listeners was:
  AnimationController#46a62(▶ -7.009; for DrivenScrollActivity)
═══════════════════════════════════════════════════════════════════
═════════════════════════════════

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.

Another exception was thrown:
RangeMaintainingScrollPhysics.applyBoundaryConditions returned
invalid overscroll value.
xuelongqy commented 2 years ago

Can you give an example

dehypnosis commented 2 years ago

Something i missed to tell is that my component sometimes triggers EasyRefreshController.callRefresh() in the middle of scrollView, programtically.. in detail when i need to refresh contents after calculate cache datetime, i trigger controller to refresh but sometimes user might already have been scrolled some extent. So it happens that scrollview animates to top, then indicator animation starts.

I know it is too complicate usecase, and it can be my component's fault. so I just give you a simple diagram.

NestedScrollView(
   headerSliverBuilder: SliverOverlapAbsorber(
       handle: NestedScrollView.sliverOverlapAbsorberHandleFor(_),
       sliver: SliverAppBar(
          // has flexible title and pinned bottom
       )
   )
   body: NotificationListener( // which moves NestedScrollView's scroll position on CustomScrollView scroll update.
       PageView(
          EasyRefresh(
             CustomScrollView(
                 SliverPinnedOverlapInjector(
                          handle:
                              NestedScrollView.sliverOverlapAbsorberHandleFor(_)),
                 <EasyRefreshIndicatorHeader>,
                 Other slivers..
                 <EasyRefreshIndicatorFooter>,
             )
          )
      )
    )
)

It is fairly incomplete but i think you can guess some potential problems here. Nevertheless i made a custom ScrollPhysics as a workaround to prevent overscrolling while refreshing is ongoing and it fixed UI disrupting.


class ConditionalScrollPhysics extends ScrollPhysics {
  const ConditionalScrollPhysics({
    required this.parent,
    required this.shouldAccept,
  }) : super(parent: parent);

  @override
  final ScrollPhysics parent;
  final bool Function(ScrollMetrics position) shouldAccept;

  @override
  ConditionalScrollPhysics applyTo(ScrollPhysics? ancestor) {
    return ConditionalScrollPhysics(
      parent: buildParent(ancestor) ?? parent,
      shouldAccept: shouldAccept,
    );
  }

  // it prevents user scroll attempts while refreshing
  @override
  bool shouldAcceptUserOffset(ScrollMetrics position) {
    if (!shouldAccept(position)) return false;
    return super.shouldAcceptUserOffset(position);
  }

  // it prevents overscrolling
  @override
  double applyBoundaryConditions(ScrollMetrics position, double value) {
    var s = super.applyBoundaryConditions(position, value);
    return max(s, 0);
  }
}

and

...
EasyRefresh(
   CustomScrollView(
            controller: scrollController,
            physics: ConditionalScrollPhysics(
              parent: RangeMaintainingScrollPhysics(),
              shouldAccept: (e) {
                return NOT_REFREHSING;
              },
            ),
...