Flutter - Supports observing NestedScrollView, with greater compatibility 😈
Overview
Time files so fast, it has been 9 months since the last article introducing the function of scrollview_observer, and now scrollview_observer has also arrived 1.21.0
version, now let’s take a look at what has been updated.
GitHub: https://github.com/fluttercandies/flutter_scrollview_observer
Function points
Support NestedScrollView
The following two marjor functions of scrollview_observer are now supported for NestedScrollView, as shown in the following figure.
- Flutter — Getting the items information those are currently displaying in ScrollView
- Flutter — Scrolling to a specific item in the ScrollView!🔥
Wait for the next article to introduce the steps in detail :)
Support center
As shown in the following code, in some scenarios, you may set center of CustomScrollView to implement the chat message page.
Widget _buildScrollView() {
return CustomScrollView(
center: _centerKey,
anchor: 1,
controller: scrollController,
slivers: [
_buildSliverListView(
color: Colors.redAccent,
onBuild: (ctx) {
_sliverListCtx1 = ctx;
},
),
_buildSliverListView(
color: Colors.blueGrey,
onBuild: (ctx) {
_sliverListCtx2 = ctx;
},
),
// center
SliverPadding(padding: EdgeInsets.zero, key: _centerKey),
_buildSliverListView(
color: Colors.teal,
onBuild: (ctx) {
_sliverListCtx3 = ctx;
},
),
_buildSliverListView(
color: Colors.purple,
onBuild: (ctx) {
_sliverListCtx4 = ctx;
},
),
],
);
}
But you want to use the jumpTo index function of item. Don’t worry, scrollview_observer has good compatibility and is already supported in the 1.19.1
version. And no additional steps are required.
observeIntervalForScrolling
I believe everyone still remembers that there are three timings for automatically triggering observation:
- scrollStart
- scrollUpdate
- scrollEnd
Among them, scrollUpdate
triggers observation too frequently. In fact, the results of many observations will not be much different. In most usage scenarios, it is not very important to us.
For this reason, in this update, a new observeIntervalForScrolling
property is added to ObserverController
, which is used to set the interval for triggering observation, thereby greatly reducing unnecessary observation calculations.
There are two points to note:
- In order not to change the previous behavior, the default value is
Duration.zero
, so you need to adjust it by yourself. It is recommended to set it toDuration(milliseconds: 500)
. - This property is only valid for
scrollUpdate
.
visibleMainAxisSize
The display size of
item
As shown, each item has a fixed height of 200, and their corresponding visibleMainAxisSize
in ListView.
visibleFraction
The visible fraction of
item
withinsliver
.Calculation formula: visibleFraction = visibleMainAxisSize / paintExtent
As shown in the figure, the current visible space of SliverList
is paintExtent
and the visibleMainAxisSize
of item20
is 30
, so the visibleFraction
of item20
is 30/376
.
SliverObserveContext
Used to get BuildContext
of sliver , which is very useful in scenarios of observing the sliver .
As follows CustomScrollView configures multiple sliver .
Widget _buildScrollView() {
return CustomScrollView(
controller: scrollController,
physics: const ClampingScrollPhysics(),
slivers: [
// banner
SliverPersistentHeader(...),
// any widget in the middle
SliverObserveContextToBoxAdapter(...),
// tabBar
SliverPersistentHeader(...),
// build some SliverGird
...List.generate(modelList.length, (mainIndex) {
return _buildSectionGridView(mainIndex);
}),
],
);
}
We need to observe which SliverGrid
is currently the first, and then update the selected index of TabBar
synchronously.
/// Record each Sliver's index and BuildContext.
Map<int, BuildContext> sliverIndexCtxMap = {};
SliverViewObserver(
controller: sliverObserverController,
sliverContexts: () => sliverIndexCtxMap.values.toList(),
child: child,
onObserveViewport: (result) {
...
},
);
But during the development process, we are likely to add another layer of Sliver
to SliverGrid
to add decoration, spacing, etc., while onObserveViewport
only recognizes the outermost layer sliver , so here we use SliverObserveContext
to wrap it and become the outermost layer. In its onObserve
callback, we can get the corresponding BuildContext
and record it.
Widget _buildSectionGridView(int mainIndex) {
Widget resultWidget = SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(...),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// Got the BuildContext of SliverGrid
return Container(
...
);
},
childCount: 10,
),
);
resultWidget = SliverPadding(
padding: const EdgeInsets.all(8),
sliver: resultWidget,
);
// Wrap it with SliverObserveContext at the outermost layer to obtain
// the BuildContext of the outer layer.
resultWidget = SliverObserveContext(
onObserve: (context) {
sliverIndexCtxMap[mainIndex] = context;
},
child: resultWidget,
);
return resultWidget;
}
Keep IM message position
The keep position function currently has three modes to choose from
enum ChatScrollObserverHandleMode {
/// Regular mode
/// Such as inserting or deleting messages.
normal,
/// Generative mode
/// Such as ChatGPT streaming messages.
generative,
/// Specified mode
/// You can specify the index of the reference message in this mode.
specified,
}
This update is about the specified mode. Because the original refItemRelativeIndex
and refItemRelativeIndexAfterUpdate
parameters can only express relative indexes, but cannot express the reference coordinate system, they are abandoned.
Added refItemIndex
and refItemIndexAfterUpdate
, and combined with refIndexType
to better specify the reference to item .
Let’s first take a look at the type definition of refIndexType
.
enum ChatScrollObserverRefIndexType {
/// relativeIndex trailing
///
/// 6 | item16 | cacheExtent
/// ----------------- -----------------
/// 5 | item15 |
/// 4 | item14 |
/// 3 | item13 | displaying
/// 2 | item12 |
/// 1 | item11 |
/// ----------------- -----------------
/// 0 | item10 | cacheExtent <---- start
///
/// leading
relativeIndexStartFromCacheExtent,
/// relativeIndex trailing
///
/// 5 | item16 | cacheExtent
/// ----------------- -----------------
/// 4 | item15 |
/// 3 | item14 |
/// 2 | item13 | displaying
/// 1 | item12 |
/// 0 | item11 | <---- start
/// ----------------- -----------------
/// -1 | item10 | cacheExtent
///
/// leading
relativeIndexStartFromDisplaying,
/// Directly specify the index of item.
itemIndex,
}
As above, there are a total of three reference modes for you to choose from.
relativeIndexStartFromCacheExtent
: Calculate the index starting fromitem
in the rendering area. This mode is generally used for chat message insertion, because the inserted message must be at0
. What remains unchanged before and after the inserted message is the original latest message, and its index changes from0
to1
, thenrefItemIndex
can be set to0
, andrefItemIndexAfterUpdate
can be set to1
.elativeIndexStartFromCacheExtent
: Calculate the index starting fromitem
in the display area. This mode is relatively rarely used. It is usually combined with the observation function, because through the observation function, we can easily know whichitem
are being displayed. Suppose you change content of the firstitem
, but does not want to affect the offset of the second displayeditem
, then this mode is just suitable for the current scenario. Because the second displayeditem
remains unchanged before and after the change,refItemIndex
is set to1
, andrefItemIndexAfterUpdate
is also set to1
.itemIndex
: The easiest to understand mode among the three modes. You specify the index ofitem
used for reference, such asitem11
above has changed. If we want to maintain the position, we can use the unchangeditem12
as a reference, sorefItemIndex
andrefItemIndexAfterUpdate
are both set to12
.
Remember, no matter which reference mode you choose, one thing you need to pay attention to is that the specified reference item
needs to be rendered before and after the change, so as to ensure that the function of keeping position can take effect normally!
Finally
Through the explanation of the above examples, I believe you will have a clearer understanding of the use of scrollview_observer . Open source is not easy. If you also find this package useful, please give it a Star
👍
GitHub: https://github.com/fluttercandies/flutter_scrollview_observer
Author: https://github.com/LinXunFeng
This article ends here. Thank you all for your support. See you next time! 👋
Series
- Flutter — Getting the items information those are currently displaying in ScrollView
- Flutter — Scrolling to a specific item in the ScrollView!🔥
- Flutter — Quickly implement the effect of the chat session list, perfect 💯
- Flutter — New upgrade😱Supports observing ScrollView built by third package💪
- Flutter — Play alternately waterfall flow video 🎞
- Flutter — Keep IM message position greatly upgraded (supports generative messages like ChatGPT) 🤖
- Flutter — Anti-occlusion of form in ScrollView 🗒
- Flutter — Quickly achieve half-view exposure statistic 📊
- Flutter — How to quickly implement an contact list page (azlist) 📓
- Flutter — Supports observing NestedScrollView, with greater compatibility 😈