indexscroll_listview_builder
Enhanced ListView.builder for Flutter with powerful bidirectional index-based programmatic scrolling, precise viewport control, item alignment, offset handling, and optional customizable scrollbar.
๐ฑ Demo

Interactive demonstration showing bidirectional scrolling, auto-scroll with alignment control, and external controller buttons
โจ Features
- ๐ฏ Bidirectional scrolling: Scroll to any item by index - works perfectly both up and down the list
- ๐ Viewport-based precision: Direct viewport offset calculations for accurate positioning
- โก Off-screen item support: Scroll to items not yet rendered with smart position estimation
- ๐ฎ Automatic initial scroll: Navigate to target index on widget build via
indexToScrollTo - ๐ Offset support: Keep items before the target visible (
numberOfOffsetedItemsPriorToSelectedItem) - ๐จ Customizable alignment: Position target item anywhere in viewport with
scrollAlignment(0.0โ1.0) - ๐น๏ธ External controller: Advanced programmatic control with
IndexedScrollController - ๐ Optional scrollbar: Full customization (thumb, track, thickness, radius, orientation)
- ๐ Operation cancellation: Superseded scroll operations are cancelled to prevent interrupted animations
- ๐ฑ Smart
shrinkWrap: Automatic handling for unbounded constraints - โจ Smooth animations: Configurable duration and curve
- ๐ฌ Frame-delayed execution: Reduces layout jank during scroll operations
๐ Installation
Add to your pubspec.yaml:
dependencies:
indexscroll_listview_builder: ^2.0.3
Then import:
import 'package:indexscroll_listview_builder/indexscroll_listview_builder.dart';
๐ Quick Start
IndexScrollListViewBuilder(
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('Item #$index')),
)
๐ฏ Auto Scroll on Build
Automatically scroll to a target index when the widget builds:
IndexScrollListViewBuilder(
itemCount: 50,
indexToScrollTo: 25, // scroll after first frame
numberOfOffsetedItemsPriorToSelectedItem: 2, // keep previous 2 items visible
itemBuilder: (context, index) => ListTile(
title: Text('Item #$index'),
),
)
Mixing Declarative and Imperative Scrolling
When using both indexToScrollTo (declarative) and controller.scrollToIndex() (imperative), you may want to force re-scrolling to the same index:
final controller = IndexedScrollController();
IndexScrollListViewBuilder(
controller: controller,
itemCount: 100,
indexToScrollTo: 25,
forceAutoScroll: true, // Force scroll even if indexToScrollTo unchanged
itemBuilder: (context, index) => ListTile(title: Text('Item #$index')),
)
// Later, scroll programmatically
await controller.scrollToIndex(75, itemCount: 100);
// Rebuild with same indexToScrollTo will still scroll back to 25
setState(() {}); // forceAutoScroll makes this re-scroll to index 25
๐งญ External Controller
Use an IndexedScrollController for programmatic control:
final controller = IndexedScrollController();
final itemCount = 100;
IndexScrollListViewBuilder(
controller: controller,
itemCount: itemCount,
itemBuilder: (context, index) => ListTile(title: Text('Item #$index')),
);
// Later (e.g. button press)
await controller.scrollToIndex(75, itemCount: itemCount, alignmentOverride: 0.3);
// Scroll to first item
await controller.scrollToIndex(0, itemCount: itemCount);
// Scroll to last item
await controller.scrollToIndex(itemCount - 1, itemCount: itemCount);
๐ช Scrollbar Example
IndexScrollListViewBuilder(
itemCount: 80,
showScrollbar: true,
scrollbarThumbVisibility: true,
scrollbarThickness: 8,
scrollbarRadius: const Radius.circular(8),
itemBuilder: (context, index) => ListTile(title: Text('Item #$index')),
)
๐ Alignment & Offset
scrollAlignment
Controls where the target item appears in the viewport:
0.0- Item aligns at the start (top for vertical, left for horizontal)0.5- Item appears centered in the viewport1.0- Item aligns at the end (bottom for vertical, right for horizontal)- Default:
0.2(20% from start)
numberOfOffsetedItemsPriorToSelectedItem
Shifts the scroll position backward to keep previous items visible:
1- Shows the target item (default)2- Shows 1 item before the target3- Shows 2 items before the target- etc.
Example:
IndexScrollListViewBuilder(
indexToScrollTo: 50,
numberOfOffsetedItemsPriorToSelectedItem: 3, // Shows items 48, 49, 50
scrollAlignment: 0.0, // Items 48-50 appear at top
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
)
๐งช Example Application
See the complete interactive example in example/lib/main.dart demonstrating:
- Basic List: Simple list with 100 items
- Auto-scroll Demo: Dynamic target selection with slider, offset control, and alignment settings
- External Controller: Comprehensive button controls:
- Scroll to First/Last item
- Jump +10/-10 items
- Direct index input
- Perfect handling of list boundaries
Run the example:
cd example
flutter run
๐ API Overview
IndexScrollListViewBuilder
Primary widget that extends ListView.builder with index-based scrolling capabilities.
Key Methods:
- Automatically wraps items with
IndexedScrollTagfor registration - Handles viewport constraints and
shrinkWraplogic - Manages scroll controller lifecycle
IndexedScrollController
Core controller that powers the scrolling mechanism.
Key Methods:
scrollToIndex(int index, {required int itemCount, double? alignmentOverride})- Scroll to specific indexregister(int index, GlobalKey key)- Register an item (called internally)unregister(int index)- Unregister an item (called internally)
Features:
- Maintains registry of
GlobalKeys for each list item - Smart index resolution with fallback logic
- Viewport-based offset calculation
- Operation versioning for cancellation
- Special handling for list extremes (first/last items)
IndexedScrollTag
Internal widget that tags each list item for the controller.
Lifecycle:
- Registers item on
initState - Updates registration on index/controller changes
- Unregisters on
dispose
โ Parameters
Core Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
itemCount |
int |
Required | Total number of items in the list |
itemBuilder |
Widget Function(BuildContext, int) |
Required | Builder function for list items |
indexToScrollTo |
int? |
null |
Auto-scroll target index after build |
forceAutoScroll |
bool |
false |
Force re-scroll to indexToScrollTo even if value unchanged |
controller |
IndexedScrollController? |
null |
External controller for programmatic scrolling |
Scrolling Behavior
| Parameter | Type | Default | Description |
|---|---|---|---|
numberOfOffsetedItemsPriorToSelectedItem |
int |
1 |
Number of items to keep visible before target |
scrollAlignment |
double? |
0.2 |
Target item alignment in viewport (0.0โ1.0) |
scrollAnimationDuration |
Duration |
400ms |
Animation duration for scrolling |
scrollDirection |
Axis? |
Axis.vertical |
Scroll direction (vertical/horizontal) |
physics |
ScrollPhysics? |
BouncingScrollPhysics |
Scroll physics |
shrinkWrap |
bool? |
Auto | Whether to shrink-wrap content (auto-detected) |
Scrollbar Customization
| Parameter | Type | Default | Description |
|---|---|---|---|
showScrollbar |
bool |
false |
Whether to display scrollbar |
scrollbarThumbVisibility |
bool? |
null |
Force scrollbar thumb visibility |
scrollbarTrackVisibility |
bool? |
null |
Force scrollbar track visibility |
scrollbarThickness |
double? |
Platform default | Scrollbar thickness in pixels |
scrollbarRadius |
Radius? |
Platform default | Scrollbar corner radius |
scrollbarOrientation |
ScrollbarOrientation? |
null |
Which side to show scrollbar |
scrollbarInteractive |
bool? |
true |
Whether scrollbar can be dragged |
suppressPlatformScrollbars |
bool |
false |
Hide platform-specific scrollbars |
Advanced Options
| Parameter | Type | Default | Description |
|---|---|---|---|
padding |
EdgeInsetsGeometry? |
EdgeInsets.zero |
List content padding |
autoScrollMaxFrameDelay |
int? |
Controller default | Max frames to wait before auto-scroll |
autoScrollEndOfFrameDelay |
int? |
Controller default | Frames to wait at end of auto-scroll |
๏ฟฝ Technical Details
How It Works
- Registration System: Each list item is wrapped with
IndexedScrollTagthat registers aGlobalKeywith the controller - Index Resolution: When scrolling to an index, the controller finds the nearest registered key using smart fallback logic
- Viewport Calculation: Uses
RenderAbstractViewport.getOffsetToRevealto calculate precise scroll offsets - Off-screen Estimation: For items not yet rendered, estimates position based on visible items and animates there
- Operation Versioning: Each scroll operation gets a version number; superseded operations are cancelled
Edge Cases Handled
- First item (index 0): Always scrolls to offset
0.0for perfect alignment - Last item: Uses
maxScrollExtentto ensure full visibility - Rapid scrolling: Operation cancellation prevents interrupted animations
- Off-screen items: Position estimation enables scrolling before item is built
- Dynamic lists: Handles controller and index changes gracefully
Performance Considerations
- Fast-path optimization: Checks exact index before searching all registered keys
- Const constructors: All widgets use
constconstructors where possible - Key caching: GlobalKeys are created once and reused across rebuilds
- Frame-delayed execution: Reduces layout jank during scroll operations
๏ฟฝ๐ CHANGELOG
See CHANGELOG.md for detailed version history.
๐ License
Licensed under the MIT License. See LICENSE.
๐ Repository & Issues
Repository: https://github.com/SoundSliced/indexscroll_listview_builder
Issues: https://github.com/SoundSliced/indexscroll_listview_builder/issues
๐ Contributing
Contributions welcome! Feel free to open issues or PRs for improvements, examples, or documentation refinements.
If this package helps you, Like it on Pub.dev, and add a โญ on GitHub. This is appreciated!
โ FAQ
Q: Can I scroll to items that haven't been built yet?
A: Yes! Version 2.0.0 estimates the position of off-screen items and scrolls there smoothly.
Q: Why does scrollToIndex require itemCount in v2.0.0?
A: The itemCount parameter enables accurate position estimation for off-screen items, especially when scrolling to the last item or items near the end.
Q: How do I scroll to the exact last item?
A: Use controller.scrollToIndex(itemCount - 1, itemCount: itemCount). The controller automatically uses maxScrollExtent for the last index.
Q: What happens if I scroll rapidly or drag a slider?
A: Version 2.0.0 includes operation versioning that cancels superseded scroll operations, ensuring smooth animations without interruption.
Q: Can I use this with horizontal lists?
A: Yes! Set scrollDirection: Axis.horizontal and the package handles everything correctly.
Q: Does this work with dynamic lists that change size?
A: Yes, the registration system automatically handles items being added or removed. The controller maintains a registry that updates as widgets are built/disposed.
Q: How do I mix declarative (indexToScrollTo) and imperative (controller.scrollToIndex) scrolling?
A: Use the forceAutoScroll: true parameter. This forces the list to re-scroll to indexToScrollTo on every rebuild, even if the value hasn't changed. This is useful when you programmatically scroll away using the controller but want rebuilds to restore the original scroll position.
Libraries
- indexscroll_listview_builder
- A Flutter package providing an enhanced ListView.builder with bidirectional index-based scrolling.