Flutter - New upgrade😱Supports observing ScrollView built by third package💪

LinXunFeng
6 min readNov 4, 2023

--

Overview

Thank you for your support of scrollview_observer. It has now become more complete and powerful. Let us take a look. Let’s see what updates have been made 🥳

Version Information

Customize the timing of triggering observation

Before this feature was added, the ScrollView would be observed throughout the scrolling process, and all items being displayed would be found through calculation. The [onObserve] and [onObserveAll] callbacks would be called only when the object of the item being displayed changes.

But often in some scenarios, we want to observe in a specified scrolling state and specify the triggering time of the observation callback. So what should we do?

1 autoTriggerObserveTypes parameter

Now you can set the timing for automatically triggering observation through this parameter, which is defined as follows:

final List<ObserverAutoTriggerObserveType>? autoTriggerObserveTypes;
enum ObserverAutoTriggerObserveType {
scrollStart,
scrollUpdate,
scrollEnd,
}

Its default value is [.scrollStart, .scrollUpdate, .scrollEnd]

Enumeration value description:

  • scrollStart: The ScrollView has started scrolling.
  • scrollUpdate: The ScrollView has changed its scroll position.
  • scrollEnd: The ScrollView has stopped scrolling.

For example, in the video list page scenario, we may only need to observe which item is the first when the ListView ends scrolling, and then automatically play the current first video. At this time, we only need to set autoTriggerObserveTypes to .scrollEnd, and at the same time avoid lots of unnecessary calculations.

2 triggerOnObserveType parameter

This parameter is used to configure the prerequisites for triggering [onObserve] and [onObserveAll] callbacks, and is defined as follows:

final ObserverTriggerOnObserveType triggerOnObserveType;
enum ObserverTriggerOnObserveType {
directly,
displayingItemsChange,
}

Its default value is .displayingItemsChange

Enumeration value description:

  • directly: Return the data directly after observing the data.
  • displayingItemsChange: Returns observed data when items on ScrollView enter or exit or when their quantity changes.

It is generally used when obtaining the item’s leadingMarginToViewport (the distance between the top of the item and the top of the viewport) and trailingMarginToViewport (the distance between the bottom of the item and the bottom of the viewport) of the item in real time.

Enhanced feature of keeping IM session position

Previously, it was limited to only maintaining the session position when inserting one message. Now it supports inserting multiple messages at the same time. The usage remains the same. The effect is as follows.

Note: This function requires the latest message item before the message is inserted as a reference to calculate the offset. Therefore, if too many messages are inserted at one time and the reference message view cannot be rendered, this function will fail and you need to set a reasonable value for ScrollView’s cacheExtent to try to avoid this problem!

Support observing waterfall flow

Originally only for regular grid layouts like GridView, only items with the same top offset were processed. Now the internal processing logic has been adjusted to support the waterfall flow.

As shown in the red line in the figure above, the first items are grid item2 and grid item3.

Support observing viewport

Everyone will encounter this situation more or less. There are many slivers placed in CustomScrollView. They need to be observed to determine which slivers are being displayed, rather than just observing the items in SliverList and SliverGrid.

Here you can use the onObserveViewport callback to get the results you want, the code is as follows

SliverViewObserver(
child: _buildScrollView(),
sliverContexts: () {
return [
if (grid1Context != null) grid1Context!,
if (swipeContext != null) swipeContext!,
if (grid2Context != null) grid2Context!,
];
},
onObserveViewport: (result) {
firstChildCtxInViewport = result.firstChild.sliverContext;
if (firstChildCtxInViewport == grid1Context) {
debugPrint('current first sliver in viewport - gridView1');
} else if (firstChildCtxInViewport == swipeContext) {
debugPrint('current first sliver in viewport - swipeView');
} else if (firstChildCtxInViewport == grid2Context) {
debugPrint('current first sliver in viewport - gridView2');
}
},
)
  1. Return the BuildContext of the sliver to be observed in the sliverContexts callback.
  2. Obtain the observation results in the onObserveViewport callback, and the corresponding class is SliverViewportObserveModel
class SliverViewportObserveModel {
/// The viewport of the current CustomScrollView.
final RenderViewportBase viewport;

/// The observing data of the first child widget that is displaying.
final SliverViewportObserveDisplayingChildModel firstChild;

/// Stores observing model list of displaying children widgets.
final List<SliverViewportObserveDisplayingChildModel>
displayingChildModelList;
...
}

What needs to be noted here is that the observed sliver should not be a nested sliver, as shown in the following code:

SliverPadding(
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(title: Text('index - $index'));
},
),
),
padding: const EdgeInsets.all(8),
);

What you need to observe is SliverPadding, not SliverList. You need to pay attention when returning data in the sliverContexts callback, otherwise the observation will be invalid.

How to get the BuildContext of that Sliver?

You can use GlobalKey, but it is recommended to use SliverLayoutBuilder here

SliverLayoutBuilder(
builder: (context, _) {
// Record the BuildContext
if (sliverCtx != context) sliverCtx = context;

SliverPadding(
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
...
),
),
padding: const EdgeInsets.all(8),
);
},
);

Support custom observation object and observation logic

1 customTargetRenderSliverType callback

Only supports ListViewObserver and GridViewObserver

While maintaining the original observation logic, tell scrollview_observer the RenderSliver type to be processed, in order to support the observation of ScrollView built by third-party package.

customTargetRenderSliverType: (renderObj) {
// Tell the package what type of RenderObject it needs to observe
// Here is an example of observing the ListView in the loading_more_list package.
return renderObj is ExtendedRenderSliverList;
},

Here are several RenderObjects commonly used in loading_more_list for your reference.

  • ListView: ExtendedRenderSliverList
  • GridView: ExtendedRenderSliverGrid
  • WaterfallFlow: RenderSliverWaterfallFlow

2 customHandleObserve callback

This callback is used to customize observation logic and is used when the built-in processing logic does not meet your needs.

customHandleObserve: (context) {
// Here you can customize the observation logic.
final _obj = context.findRenderObject();
if (_obj is RenderSliverList) {
// You can use the default processing method provided by scrollview_observer
ObserverCore.handleListObserve(context: context);
}
if (_obj is RenderSliverGrid || _obj is RenderSliverWaterfallFlow) {
// You can use the default processing method provided by scrollview_observer
return ObserverCore.handleGridObserve(context: context);
}
// Process some Slivers built by third-party package
...

// Other types will not be processed
return null;
},

The original processing logic of RenderSliverList and RenderSliverGride has been extracted into ObserverCore for everyone to freely combine and use.

3 extendedHandleObserve callback

Only supports SliverViewObserver

This callback is used to supplement the original observation logic, which originally only processed RenderSliverList, RenderSliverFixedExtentList and RenderSliverGrid.

extendedHandleObserve: (context) {
// An extension of the original observation logic.
final _obj = context.findRenderObject();
if (_obj is RenderSliverWaterfallFlow) {
return ObserverCore.handleGridObserve(context: context);
}
return null;
},

Combining ObserverCore and the above function points, the function of freely observing ScrollView built by third-party package is realized, and is no longer limited to the official SliverList and SliverGrid.

This is a useful feature that allows you to build your ScrollView with more freedom, such as observing waterfall flow built using fluttercandies/waterfall_flow.

--

--