positioned_scroll_observer 2.1.0 positioned_scroll_observer: ^2.1.0 copied to clipboard
An elegant scroll observer that support most scroll views could jump/animateToIndex without breaking current widgets.
Features #
- Using
jumpToIndex
andanimateToIndex
to scroll to the specificindex
- No breaking for your current sliver widgets, e.g.,
ListView
/GridView
,SliverList
/SliverGrid
/SliverAppBar
, just wrapping your item widgets usingObserverProxy
. Supported:
- ✅ ListView
- ✅ GridView
- ✅ CustomScrollView
- ✅ SingleChildScrollView
- ✅ ListWheelScrollView
- ❌ NestedScrollView (waiting testing)
-
Using
RetainableScrollController
to retain the old offset to avoid scrolling when adding new items into the top ofListView
. See retain old scroll offset. -
Check if the specific
index
is visible on the screen. See check visibility.
Getting started #
-
First, creating and binding the observer to all items. (See box scroll observer and sliver scroll observer)
-
then, using the observer like:
_observer.jumpToIndex( index, position: _controller.position, ); _observer.animateToIndex( index, position: _controller.position, duration: const Duration(milliseconds: 200), curve: Curves.fastLinearToSlowEaseIn, );
Usage for scroll views that do not rely on RenderSliver
, e.g., SingleChildScrollView
and ListWheelScrollView
#
-
create a
BoxScrollObserver
for observing the box with multi children.final ScrollController _controller = ScrollController(); late final _observer = ScrollObserver.boxMulti( axis: _axis, itemCount: 30, );
-
bind the observer to the box's children. (Using
ObserverProxy
to wrap each item).SingleChildScrollView( controller: _controller, scrollDirection: _axis, child: Column( children: [ for (int i = 0; i < 30; i++) ObserverProxy( observer: _observer, child: DecoratedBox( decoration: BoxDecoration(border: Border.all()), child: SizedBox( height: 100, width: 100, child: Center( child: Text("Column item $i"), ), ), ), ), ], ), );
Usage for slivers widgets. #
-
create a
SliverScrollObserver
for observing the sliver with multi children.final ScrollController _controller = ScrollController(); late final _observer = ScrollObserver.sliverMulti(itemCount: 30);
-
bind the observer to each item for the sliver.
ListView.builder( controller: _controller, itemBuilder: (context, index) => ObserverProxy( observer: _observer, child: ListTile( key: ValueKey<int>(index), leading: const CircleAvatar( child: Text("L"), ), title: Text("Positioned List Example $index"), ), ), itemCount: _itemCount, );
For
ListView.custom
andGridView.custom
, you could also usePositionedChildListDelegate
andPositionedChildBuilderDelegate
for wrapping items inObserverProxy
conveniently
Usage #
For observing slivers: #
- observing a sliver with single child, using
ScrollObserver.sliverSingle
to create. - observing a sliver with multi children, using
ScrollObserver.sliverMulti
to create.
For observing other scroll views that have no sliver descendants. #
- observing a box with single child, using
ScrollObserver.boxSingle
to create. (rare cases and need more testing) - observing a box with multi children, using
ScrollObserver.boxMulti
to create.
Checking index
is visible on the screen #
By invoking YourObserver.isRevealed
to check if the index
's RenderObject
is visible on the screen.
index
: your specific indexscrollExtent
: You could get theScrollExtent
by usingScrollExtent.fromPosition(ScrollController.position)
strategy
indicates how to determine if theindex
is visible.PredicatorStrategy.tolerance
would tolerate the visual ratio is not less50%
PredicatorStrategy.inside
would ensure the entireindex
is visible
More details, see API reference.
Using RetainableScrollController
for retaining the old scroll offset #
- create a
RetainableScrollController
final RetainableScrollController _controller = RetainableScrollController();
- using it when you do not want the list to scroll if inserting a new item at
0
index
_items.insert(
0,
ObserverProxy(
observer: _observer,
child: ListTile(
leading: const CircleAvatar(
child: Text("L"),
),
title: Text("Positioned List Example $_itemCount"),
),
),
);
_controller.retainOffset();
setState(() {});
Pay attention #
- The item widget/builder must be wrapped using
ObserverProxy
- All observers would
normalizeIndex
to ensure theindex
is in a valid range determined byitemCount
of observers, so developers should also update observers'itemCount
when the scroll views' item count changes. - Items that have the same
RenderObject
observed by an observer should share the same observer instance, instead of creating different observers for each item. - When using
ScrollObserver.boxMulti
,axis
is required so that the observer could estimate the scroll offset along the correct main axis.
Examples: #
- ListView example
- GridView example
- CustomScrollView example
- ReorderableListView example
- ListWheelScrollView example
- SingleChildScrollView example
FAQ #
TODO
Contributions #
Feel free to contribute to this project.
If you find a bug or want a feature, but don't know how to fix/implement it, please fill an issue.
If you fixed a bug or implemented a feature, please send a pull request