I want to display a finite number of items and wrap when the user scrolls in either direction. How do I do this?
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 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 {
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: [
new CustomScrollView(
physics: new AlwaysScrollableScrollPhysics(),
controller: _negativeController,
reverse: true,
slivers: [
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);
},
),
],
);
}
}