I want to display a finite number of items and wrap when the user scrolls in either direction. How do I do this?
Asked
Active
Viewed 9,296 times
2 Answers
3
You can't easily solve this with an infinite-length ListView.builder
because it only goes in one direction. If you want to wrap in both directions, it is possible to simulate bidirectional wrapping with a Stack
of two viewports going in opposite directions.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new HomePage(),
));
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Wrapping List View'),
),
body: new WrappingListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new Container(
height: 50.0,
color: Colors.blue.withOpacity(index / 10),
child: new Center(
child: new Text('Card $index')
),
),
);
},
),
);
}
}
class WrappingListView extends StatefulWidget {
factory WrappingListView({ Key key, List<Widget> children }) {
return new WrappingListView.builder(
itemCount: children.length,
itemBuilder: (BuildContext context, int index) {
return children[index % children.length];
},
);
}
WrappingListView.builder({ Key key, this.itemBuilder, this.itemCount })
: super(key: key);
final int itemCount;
final IndexedWidgetBuilder itemBuilder;
WrappingListViewState createState() => new WrappingListViewState();
}
class UnboundedScrollPosition extends ScrollPositionWithSingleContext {
UnboundedScrollPosition({
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
}) : super(physics: physics, context: context, oldPosition: oldPosition);
@override
double get minScrollExtent => double.negativeInfinity;
}
class UnboundedScrollController extends ScrollController {
@override
UnboundedScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition,
) {
return new UnboundedScrollPosition(
physics: physics,
context: context,
oldPosition: oldPosition,
);
}
}
class WrappingListViewState extends State<WrappingListView> {
UnboundedScrollController _controller = new UnboundedScrollController();
UnboundedScrollController _negativeController = new UnboundedScrollController();
@override
void initState() {
_controller.addListener(() {
_negativeController.jumpTo(
-_negativeController.position.extentInside -
_controller.position.pixels,
);
});
}
@override
Widget build(BuildContext context) {
return new Stack(
children: <Widget>[
new CustomScrollView(
physics: new AlwaysScrollableScrollPhysics(),
controller: _negativeController,
reverse: true,
slivers: <Widget>[
new SliverList(
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return widget.itemBuilder(
context,
(widget.itemCount - 1 - index) % widget.itemCount,
);
}
),
),
],
),
new ListView.builder(
controller: _controller,
itemBuilder: (BuildContext context, int index) {
return widget.itemBuilder(context, index % widget.itemCount);
},
),
],
);
}
}

Marcelo Glasberg
- 29,013
- 23
- 109
- 133

Collin Jackson
- 110,240
- 31
- 221
- 152
-
By using this technique, the cards inside of the top list can't be tapped, because the list below is absorbing the events. Really, this solution only works if you don't want gesture interactions with the items. – Marcelo Glasberg Aug 10 '18 at 00:40
1
As of December 2018 you can use this (with a suitable builder): https://pub.dartlang.org/packages/infinite_listview

Marcelo Glasberg
- 29,013
- 23
- 109
- 133