flutter_scrollview_observer

repository·main·Indexed 18 days ago

https://github.com/fluttercandies/flutter_scrollview_observer

A Flutter library for observing and interacting with child widgets inside various ScrollViews, including PageView, ListView, GridView, and NestedScrollView. It provides tools to track visible items, programmatically scroll to specific indices via ObserverController, and implement chat-like scrolling behaviors and IM message stability. Key components include ObserverWidget for visibility monitoring and ObserverIndexPositionModel for tracking item positions.

Tokens
11.6K
Snippets
36
Records
42
Agent score
69%

What's inside flutter_scrollview_observer

  1. Overview of Flutter ScrollView Observer features

    main

    The flutter_scrollview_observer library provides widgets that allow you to monitor child widgets currently visible within a scrollable area. Instead of refactoring your existing UI, you can simply wrap your current scroll view with a ViewObserver to enable several features:

    • Observe visible child widgets: Detect which items are currently being displayed in a ScrollView.
    • Scroll to specific items: Programmatically scroll to a specific index or location within the ScrollView.
    • Chat session effects: Quickly implement common chat UI behaviors.
    • IM message stability: Maintain message positions when inserting or updating messages (e.g., in a chat app) to prevent UI jitter.
  2. Install the scrollview_observer package

    main

    To use this library in your Flutter project, add scrollview_observer to your pubspec.yaml dependencies. You can use latest_version or specify a specific version.

    After adding the dependency, import the package in the Dart files where you intend to use its widgets:

    dependencies:
      scrollview_observer: latest_version
    import 'package:scrollview_observer/scrollview_observer.dart';
  3. Use ObserverController to manage scroll observation

    main

    The ObserverController is the primary class used to manage the state of scroll observation. It holds the target ScrollController, tracks child offsets within slivers, and manages observation intervals and callbacks.

    Key properties:

    • controller: The target ScrollController being observed.
    • observeIntervalForScrolling: A Duration that defines the minimum wait time before firing observation callbacks during scrolling (defaults to Duration.zero).
    • isForbidObserveCallback: A boolean flag to disable onObserve and onObserveAll callbacks.
    • indexOffsetMap: An internal map storing the offsets of children in the sliver.
    ObserverController controller = ObserverController(controller: myScrollController);
  4. How ChatScrollObserver handles different update modes

    main

    When calling standby(), the mode parameter determines how the observer identifies the 'anchor' item to maintain scroll stability:

    1. ChatScrollObserverHandleMode.normal: Automatically uses the first item in the list as the reference point. Useful for simple prepending/appending logic.
    2. ChatScrollObserverHandleMode.generative: Calculates the reference based on the first item's index plus the changeCount. This is useful when new items are being generated at the start of the list.
    3. ChatScrollObserverHandleMode.specified: The most precise mode. It allows you to target a specific message by its index. You can provide the index directly (refItemIndex) or use a relative index (refItemRelativeIndex) based on the refIndexType (e.g., relative to the currently displaying items or the cache extent).
  5. Manage nested scroll interactions with NestedScrollUtil

    main

    When working with NestedScrollView, you often need to coordinate scrolling between the header slivers and the body slivers. NestedScrollUtil is a utility class designed to manage these interactions, specifically helping to switch between the outerScrollController (header) and the bodyScrollController (body) when performing scroll operations like jumping or animating to a specific index.

    To use it effectively, you must ensure the utility has been populated with the correct BuildContexts and ScrollControllers for your NestedScrollView instance.

    // Note: This utility is typically used in conjunction with a NestedScrollView setup.
    // It requires the outerScrollController and bodyScrollController to be non-null.
    // to perform operations like jumpTo or animateTo.
  6. Use SliverViewObserver to observe sliver scroll behavior

    main

    The SliverViewObserver widget is used to monitor the scroll behavior of slivers (such as RenderSliverList, RenderSliverFixedExtentList, and RenderSliverGrid) within a viewport. It provides callbacks to track which slivers are currently being displayed and general observation of sliver changes.

    Key features include:

    • onObserveViewport: A callback that returns a SliverViewportObserveModel containing information about the slivers currently visible in the viewport.
    • sliverContexts: A function that provides the BuildContext for each sliver, allowing you to identify them.
    • Custom Observation: You can provide customHandleObserve or extendedHandleObserve to implement custom logic for slivers not natively supported by the observer.
    • Trigger Control: Use triggerOnObserveType to control when observation callbacks are fired.
    SliverViewObserver(
      sliverContexts: () => mySliverContexts,
      onObserveViewport: (model) {
        // model contains viewport and displaying sliver information
        print('First visible sliver: ${model.firstChild.sliver}');
      },
      child: CustomScrollView(
        slivers: [
          // your slivers here
        ],
      ),
    );
  7. Clear scroll index cache

    main

    If you have enabled cacheJumpIndexOffset (which is true by default), the controller stores offsets for indices to speed up subsequent jumps. Use clearScrollIndexCache to wipe this cache for a specific sliver context.

    Note: clearIndexOffsetCache is deprecated and will be removed in version 2; use clearScrollIndexCache instead.

    controller.clearScrollIndexCache(sliverContext: mySliverContext);
  8. Prepare ChatScrollObserver for content updates with standby()

    main

    Call standby() to prepare the observer for an upcoming change in the scroll view's children (like inserting or removing messages). This method calculates the necessary offsets and indices to maintain the user's scroll position.

    Parameters

    • sliverContext: The BuildContext of the sliver.
    • isRemove: Set to true if messages are being removed; otherwise, false (default).
    • changeCount: The number of messages added (used when isRemove is false).
    • mode: The ChatScrollObserverHandleMode determining how to find the reference item:
      • ChatScrollObserverHandleMode.normal: Uses the first item as the reference.
      • ChatScrollObserverHandleMode.generative: Calculates based on the first item plus changeCount.
      • ChatScrollObserverHandleMode.specified: Uses specific indices provided via refItemIndex or refItemRelativeIndex.
    • refIndexType: Determines how refItemRelativeIndex is interpreted:
      • ChatScrollObserverRefIndexType.relativeIndexStartFromCacheExtent
      • ChatScrollObserverRefIndexType.relativeIndexStartFromDisplaying
      • ChatScrollObserverRefIndexType.itemIndex
    • refItemIndex: The index of the reference message before the update.
    • refItemIndexAfterUpdate: The index of the same reference message after the update.
    • customAdjustPosition: (Optional) A custom function to define how the scroll position is adjusted.
    • customAdjustPositionDelta: (Optional) A custom function to define the delta for position adjustment.
    await observer.standby(
      sliverContext: context,
      isRemove: false,
      changeCount: 1,
      mode: ChatScrollObserverHandleMode.specified,
      refItemIndex: 10, // The index of the message before insertion
      refItemIndexAfterUpdate: 11, // The index of the same message after insertion
    );
  9. Use ObserverWidget to observe scroll views

    main

    The ObserverWidget is the primary component used to observe the scroll position and visibility of items within a scrollable widget (like ListView or GridView). It wraps the scrollable widget and provides callbacks when the observed items change.

    Key Properties

    • child: The scrollable widget you want to observe.
    • onObserve: Callback triggered when the first observed sliver's state changes.
    • onObserveAll: Callback triggered when any observed sliver's state changes, providing a map of all results.
    • sliverController: An optional controller to dispatch notifications or manage observation.
    • tag: A unique string used to identify this specific ObserverWidget instance when multiple observers are nested.
    • leadingOffset: A fixed offset used for calculations.
    • dynamicLeadingOffset: A callback to calculate the offset dynamically.
    • toNextOverPercent: A threshold (0.0 to 1.0) determining when the next child becomes the 'first' child based on how much of its size is visible.
    • scrollNotificationPredicate: A predicate to filter which ScrollNotifications trigger observation (useful for performance).
    • autoTriggerObserveTypes: A list of ObserverAutoTriggerObserveType (e.g., .scrollStart, .scrollUpdate, .scrollEnd) that trigger automatic observation.
    • triggerOnObserveType: Determines the prerequisite for triggering onObserve. Defaults to ObserverTriggerOnObserveType.displayingItemsChange.
    • customTargetRenderSliverType: A predicate to define which RenderObject types should be treated as target slivers (defaults to RenderSliverList, RenderSliverFixedExtentList, and RenderSliverGrid).
    • customHandleObserve: Allows providing custom logic to generate the observation model M for a given BuildContext.
    ObserverWidget(
      child: ListView.builder(
        itemBuilder: (context, index) => Text('Item $index'),
        itemCount: 100,
      ),
      onObserve: (model) {
        print('First item changed: $model');
      },
      onObserveAll: (map) {
        print('All items changed: $map');
      },
    );
  10. Animate to an index in a NestedScrollView using NestedScrollUtil.animateTo

    main

    Use NestedScrollUtil.animateTo to smoothly scroll to a specific index within a NestedScrollView. This is useful for providing a polished user experience when navigating long lists in a nested structure.

    Parameters:

    • nestedScrollViewKey: The GlobalKey associated with your NestedScrollView.
    • observerController: The SliverObserverController managing the scroll view.
    • position: A NestedScrollUtilPosition indicating whether to target the header or the body.
    • index: The target index to animate to.
    • duration: The Duration of the animation.
    • curve: The Curve to use for the animation.
    • sliverContext: The BuildContext of the sliver.
    • isFixedHeight: Whether the item has a fixed height.
    • alignment: The alignment of the target item (default is 0).
    • padding: Padding to apply (default is EdgeInsets.zero).
    • offset: An optional ObserverLocateIndexOffsetCallback for custom offset calculation.
    • renderSliverType: The type of render sliver to use.
    await NestedScrollUtil().animateTo(
      nestedScrollViewKey: myNestedKey,
      observerController: myObserverController,
      position: NestedScrollUtilPosition.body,
      index: 10,
      duration: Duration(milliseconds: 300),
      curve: Curves.easeInOut,
      sliverContext: mySliverContext,
    );
  11. Listen to the scrolling task lifecycle

    main

    The package provides a hierarchy of notifications to track the lifecycle of a scrolling task (typically initiated via jumpTo or animateTo on an ObserverController).

    To monitor a scroll task, you can listen for these specific notifications in this sequence:

    1. ObserverScrollStartNotification: The scrolling task has started.
    2. ObserverScrollDecisionNotification: The data for a specific index item has been determined during the scroll.
    3. ObserverScrollEndNotification: The scrolling task has completed.

    Additionally, you can listen for ObserverScrollInterruptionNotification if a scrolling task is interrupted by a new scroll command.

    // Conceptual usage of listening to scroll lifecycle
    listener(Notification notification) {
      if (notification is ObserverScrollStartNotification) {
        // Handle start
      } else if (notification is ObserverScrollDecisionNotification) {
        // Handle decision/index determination
      } else if (notification is ObserverScrollEndNotification) {
        // Handle end
      } else if (notification is ObserverScrollInterruptionNotification) {
        // Handle interruption
      }
    }