Flutter: Scrolling to a widget in ListView
Solution 1:
By far, the easiest solution is to use Scrollable.ensureVisible(context)
. As it does everything for you and work with any widget size. Fetching the context using GlobalKey
.
The problem is that ListView
won't render non-visible items. Meaning that your target most likely will not be built at all. Which means your target will have no context
; preventing you from using that method without some more work.
In the end, the easiest solution will be to replace your ListView
by a SingleChildScrollView
and wrap your children into a Column
. Example :
class ScrollView extends StatelessWidget {
final dataKey = new GlobalKey();
@override
Widget build(BuildContext context) {
return new Scaffold(
primary: true,
appBar: new AppBar(
title: const Text('Home'),
),
body: new SingleChildScrollView(
child: new Column(
children: <Widget>[
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
// destination
new Card(
key: dataKey,
child: new Text("data\n\n\n\n\n\ndata"),
)
],
),
),
bottomNavigationBar: new RaisedButton(
onPressed: () => Scrollable.ensureVisible(dataKey.currentContext),
child: new Text("Scroll to data"),
),
);
}
}
NOTE : While this allows to scroll to the desired item easily, consider this method only for small predefined lists. As for bigger lists you'll get performance problems.
But it's possible to make Scrollable.ensureVisible
work with ListView
; although it will require more work.
Solution 2:
Unfortunately, ListView has no built-in approach to a scrollToIndex() function. You’ll have to develop your own way to measure to that element’s offset for animateTo()
or jumpTo()
, or you can search through these suggested solutions/plugins or from other posts like flutter ListView scroll to index not available
(the general scrollToIndex issue is discussed at flutter/issues/12319 since 2017, but still with no current plans)
But there is a different kind of ListView that does support scrollToIndex:
-
ScrollablePositionedList
- dependency: scrollable_positioned_list
You set it up exactly like ListView and works the same, except you now have access to a ItemScrollController that does:
jumpTo({index, alignment})
scrollTo({index, alignment, duration, curve})
Simplified example:
ItemScrollController _scrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: _myList.length,
itemBuilder: (context, index) {
return _myList[index];
},
)
_scrollController.scrollTo(index: 150, duration: Duration(seconds: 1));
Please not that although the
scrollable_positioned_list
package is published by google.dev, they explicitly state that their packages are not officially supported Google products. - Source
Solution 3:
Screenshot (Fixed height content)
If your items have fixed height, then you can use the following approach.
class HomePage extends StatelessWidget {
final ScrollController _controller = ScrollController();
final double _height = 100.0;
void _animateToIndex(int index) {
_controller.animateTo(
index * _height,
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.arrow_downward),
onPressed: () => _animateToIndex(10),
),
body: ListView.builder(
controller: _controller,
itemCount: 20,
itemBuilder: (_, i) {
return SizedBox(
height: _height,
child: Card(
color: i == 10 ? Colors.blue : null,
child: Center(child: Text('Item $i')),
),
);
},
),
);
}
}
Solution 4:
For people are trying to jump to widget in CustomScrollView. First, add this plugin to your project.
Then look at my example code below:
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
AutoScrollController _autoScrollController;
final scrollDirection = Axis.vertical;
bool isExpaned = true;
bool get _isAppBarExpanded {
return _autoScrollController.hasClients &&
_autoScrollController.offset > (160 - kToolbarHeight);
}
@override
void initState() {
_autoScrollController = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: scrollDirection,
)..addListener(
() => _isAppBarExpanded
? isExpaned != false
? setState(
() {
isExpaned = false;
print('setState is called');
},
)
: {}
: isExpaned != true
? setState(() {
print('setState is called');
isExpaned = true;
})
: {},
);
super.initState();
}
Future _scrollToIndex(int index) async {
await _autoScrollController.scrollToIndex(index,
preferPosition: AutoScrollPosition.begin);
_autoScrollController.highlight(index);
}
Widget _wrapScrollTag({int index, Widget child}) {
return AutoScrollTag(
key: ValueKey(index),
controller: _autoScrollController,
index: index,
child: child,
highlightColor: Colors.black.withOpacity(0.1),
);
}
_buildSliverAppbar() {
return SliverAppBar(
brightness: Brightness.light,
pinned: true,
expandedHeight: 200.0,
backgroundColor: Colors.white,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
background: BackgroundSliverAppBar(),
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(40),
child: AnimatedOpacity(
duration: Duration(milliseconds: 500),
opacity: isExpaned ? 0.0 : 1,
child: DefaultTabController(
length: 3,
child: TabBar(
onTap: (index) async {
_scrollToIndex(index);
},
tabs: List.generate(
3,
(i) {
return Tab(
text: 'Detail Business',
);
},
),
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _autoScrollController,
slivers: <Widget>[
_buildSliverAppbar(),
SliverList(
delegate: SliverChildListDelegate([
_wrapScrollTag(
index: 0,
child: Container(
height: 300,
color: Colors.red,
)),
_wrapScrollTag(
index: 1,
child: Container(
height: 300,
color: Colors.red,
)),
_wrapScrollTag(
index: 2,
child: Container(
height: 300,
color: Colors.red,
)),
])),
],
),
);
}
}
Yeah it's just a example, use your brain to make it this idea become true