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

Image for post
Image for post

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!

A JavaScript/Node/Flutter developer who love technical stuffs.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store