Flutter: Creating a two direction scrolling table with fixed head and column

Today I am going to demonstrate how to create a two direction (vertical and horizontal) scrolling data table with flutter, this is how it looks like:

If you want to view the full source code and play around with it, check here.
First of all, I have used linked_scroll_controller, let’s install it by adding it to our pubspec.yaml
:
linked_scroll_controller: ^0.1.2
Then we create a widget for table cells:
class MultiplicationTableCell extends StatelessWidget {
final int value;
final Color color;MultiplicationTableCell({
this.value,
this.color,
});@override
Widget build(BuildContext context) {
return Container(
width: cellWidth,
height: cellWidth,
decoration: BoxDecoration(
color: color,
border: Border.all(
color: Colors.black12,
width: 1.0,
),
),
alignment: Alignment.center,
child: Text(
'${value ?? ''}',
style: TextStyle(fontSize: 16.0),
),
);
}
}
Here is the table head, note that we added a parameter scrollController
to let it sync its scroll position with the table body:
class TableHead extends StatelessWidget {
final ScrollController scrollController;TableHead({
@required this.scrollController,
});@override
Widget build(BuildContext context) {
return SizedBox(
height: cellWidth,
child: Row(
children: [
MultiplicationTableCell(
color: Colors.yellow.withOpacity(0.3),
value: 1,
),
Expanded(
child: ListView(
controller: scrollController,
physics: ClampingScrollPhysics(),
scrollDirection: Axis.horizontal,
children: List.generate(maxNumber - 1, (index) {
return MultiplicationTableCell(
color: Colors.yellow.withOpacity(0.3),
value: index + 2,
);
}),
),
),
],
),
);
}
}
This is the table body, note that it also has a scrollController
parameter. Since the first column is fixed and we can scroll the rest columns horizontally, we added two ListViews here, see how we used linked_scroll_controller
to sync the scrolling positions:
class TableBody extends StatefulWidget {
final ScrollController scrollController;TableBody({
@required this.scrollController,
});@override
_TableBodyState createState() => _TableBodyState();
}class _TableBodyState extends State<TableBody> {
LinkedScrollControllerGroup _controllers;
ScrollController _firstColumnController;
ScrollController _restColumnsController;@override
void initState() {
super.initState();
_controllers = LinkedScrollControllerGroup();
_firstColumnController = _controllers.addAndGet();
_restColumnsController = _controllers.addAndGet();
}@override
void dispose() {
_firstColumnController.dispose();
_restColumnsController.dispose();
super.dispose();
}@override
Widget build(BuildContext context) {
return Row(
children: [
SizedBox(
width: cellWidth,
child: ListView(
controller: _firstColumnController,
physics: ClampingScrollPhysics(),
children: List.generate(maxNumber - 1, (index) {
return MultiplicationTableCell(
color: Colors.yellow.withOpacity(0.3),
value: index + 2,
);
}),
),
),
Expanded(
child: SingleChildScrollView(
controller: widget.scrollController,
scrollDirection: Axis.horizontal,
physics: const ClampingScrollPhysics(),
child: SizedBox(
width: (maxNumber - 1) * cellWidth,
child: ListView(
controller: _restColumnsController,
physics: const ClampingScrollPhysics(),
children: List.generate(maxNumber - 1, (y) {
return Row(
children: List.generate(maxNumber - 1, (x) {
return MultiplicationTableCell(
value: (x + 2) * (y + 2),
);
}),
);
}),
),
),
),
),
],
);
}
}
And finally, putting it all together:
class MultiplicationTable extends StatefulWidget {
@override
_MultiplicationTableState createState() => _MultiplicationTableState();
}class _MultiplicationTableState extends State<MultiplicationTable> {
LinkedScrollControllerGroup _controllers;
ScrollController _headController;
ScrollController _bodyController;@override
void initState() {
super.initState();
_controllers = LinkedScrollControllerGroup();
_headController = _controllers.addAndGet();
_bodyController = _controllers.addAndGet();
}@override
void dispose() {
_headController.dispose();
_bodyController.dispose();
super.dispose();
}@override
Widget build(BuildContext context) {
return Column(
children: [
TableHead(
scrollController: _headController,
),
Expanded(
child: TableBody(
scrollController: _bodyController,
),
),
],
);
}
}
Conclusion
So this is how we create a two direction scrollable data table in flutter. The most complicated part is there are multiple scroll controllers, you have to very clear about which ListView is responsible to which part, and not to mix them up. Click here to view the full sample source code.
If this article helped you, smash the clap button and share it anywhere to let this free article help even more people!