Solución:
Podría encontrar una solución alternativa usando controladores de desplazamiento, se ve así: Video
Básicamente es un desplazamiento horizontal para la primera fila, un desplazamiento vertical para la primera columna y un desplazamiento horizontal y vertical mixto para la subtabla. Luego, cuando mueve la subtabla, sus controladores mueven la columna y la fila.
Aquí hay un widget personalizado con un ejemplo de cómo usarlo:
final _rowsCells = [
[7, 8, 10, 8, 7],
[10, 10, 9, 6, 6],
[5, 4, 5, 7, 5],
[9, 4, 1, 7, 8],
[7, 8, 10, 8, 7],
[10, 10, 9, 6, 6],
[5, 4, 5, 7, 5],
[9, 4, 1, 7, 8],
[7, 8, 10, 8, 7],
[10, 10, 9, 6, 6],
[5, 4, 5, 7, 5],
[9, 4, 1, 7, 8],
[7, 8, 10, 8, 7],
[10, 10, 9, 6, 6],
[5, 4, 5, 7, 5],
[9, 4, 1, 7, 8]
];
final _fixedColCells = [
"Pablo",
"Gustavo",
"John",
"Jack",
"Pablo",
"Gustavo",
"John",
"Jack",
"Pablo",
"Gustavo",
"John",
"Jack",
"Pablo",
"Gustavo",
"John",
"Jack",
];
final _fixedRowCells = [
"Math",
"Informatics",
"Geography",
"Physics",
"Biology"
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: CustomDataTable(
rowsCells: _rowsCells,
fixedColCells: _fixedColCells,
fixedRowCells: _fixedRowCells,
cellBuilder: (data) {
return Text('$data', style: TextStyle(color: Colors.red));
},
),
);
}
class CustomDataTable<T> extends StatefulWidget {
final T fixedCornerCell;
final List<T> fixedColCells;
final List<T> fixedRowCells;
final List<List<T>> rowsCells;
final Widget Function(T data) cellBuilder;
final double fixedColWidth;
final double cellWidth;
final double cellHeight;
final double cellMargin;
final double cellSpacing;
CustomDataTable({
this.fixedCornerCell,
this.fixedColCells,
this.fixedRowCells,
@required this.rowsCells,
this.cellBuilder,
this.fixedColWidth = 60.0,
this.cellHeight = 56.0,
this.cellWidth = 120.0,
this.cellMargin = 10.0,
this.cellSpacing = 10.0,
});
@override
State<StatefulWidget> createState() => CustomDataTableState();
}
class CustomDataTableState<T> extends State<CustomDataTable<T>> {
final _columnController = ScrollController();
final _rowController = ScrollController();
final _subTableYController = ScrollController();
final _subTableXController = ScrollController();
Widget _buildChild(double width, T data) => SizedBox(
width: width, child: widget.cellBuilder?.call(data) ?? Text('$data'));
Widget _buildFixedCol() => widget.fixedColCells == null
? SizedBox.shrink()
: Material(
color: Colors.lightBlueAccent,
child: DataTable(
horizontalMargin: widget.cellMargin,
columnSpacing: widget.cellSpacing,
headingRowHeight: widget.cellHeight,
dataRowHeight: widget.cellHeight,
columns: [
DataColumn(
label: _buildChild(
widget.fixedColWidth, widget.fixedColCells.first))
],
rows: widget.fixedColCells
.sublist(widget.fixedRowCells == null ? 1 : 0)
.map((c) => DataRow(
cells: [DataCell(_buildChild(widget.fixedColWidth, c))]))
.toList()),
);
Widget _buildFixedRow() => widget.fixedRowCells == null
? SizedBox.shrink()
: Material(
color: Colors.greenAccent,
child: DataTable(
horizontalMargin: widget.cellMargin,
columnSpacing: widget.cellSpacing,
headingRowHeight: widget.cellHeight,
dataRowHeight: widget.cellHeight,
columns: widget.fixedRowCells
.map((c) =>
DataColumn(label: _buildChild(widget.cellWidth, c)))
.toList(),
rows: []),
);
Widget _buildSubTable() => Material(
color: Colors.lightGreenAccent,
child: DataTable(
horizontalMargin: widget.cellMargin,
columnSpacing: widget.cellSpacing,
headingRowHeight: widget.cellHeight,
dataRowHeight: widget.cellHeight,
columns: widget.rowsCells.first
.map((c) => DataColumn(label: _buildChild(widget.cellWidth, c)))
.toList(),
rows: widget.rowsCells
.sublist(widget.fixedRowCells == null ? 1 : 0)
.map((row) => DataRow(
cells: row
.map((c) => DataCell(_buildChild(widget.cellWidth, c)))
.toList()))
.toList()));
Widget _buildCornerCell() =>
widget.fixedColCells == null || widget.fixedRowCells == null
? SizedBox.shrink()
: Material(
color: Colors.amberAccent,
child: DataTable(
horizontalMargin: widget.cellMargin,
columnSpacing: widget.cellSpacing,
headingRowHeight: widget.cellHeight,
dataRowHeight: widget.cellHeight,
columns: [
DataColumn(
label: _buildChild(
widget.fixedColWidth, widget.fixedCornerCell))
],
rows: []),
);
@override
void initState() {
super.initState();
_subTableXController.addListener(() {
_rowController.jumpTo(_subTableXController.position.pixels);
});
_subTableYController.addListener(() {
_columnController.jumpTo(_subTableYController.position.pixels);
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Row(
children: <Widget>[
SingleChildScrollView(
controller: _columnController,
scrollDirection: Axis.vertical,
physics: NeverScrollableScrollPhysics(),
child: _buildFixedCol(),
),
Flexible(
child: SingleChildScrollView(
controller: _subTableXController,
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
controller: _subTableYController,
scrollDirection: Axis.vertical,
child: _buildSubTable(),
),
),
),
],
),
Row(
children: <Widget>[
_buildCornerCell(),
Flexible(
child: SingleChildScrollView(
controller: _rowController,
scrollDirection: Axis.horizontal,
physics: NeverScrollableScrollPhysics(),
child: _buildFixedRow(),
),
),
],
),
],
);
}
}
Dado que la primera columna, la primera fila y la subtabla son independientes, tuve que crear una DataTable
para cada uno. Y desde DataTable
tiene encabezados que no se pueden eliminar, los encabezados de la primera columna y la subtabla están ocultos por la primera fila.
Además, tuve que hacer que la primera columna y la primera fila no se puedan desplazar manualmente porque si las desplaza, la subtabla no se desplazará.
Puede que esta no sea la mejor solución, pero por el momento no parece haber otra forma de hacerlo. Podría intentar mejorar este enfoque, tal vez utilizando Table
u otros widgets en lugar de DataTable
al menos podría evitar ocultar los encabezados de la subtabla y la primera columna.