Flutter Resize table column by dragging

eye-catchDart and Flutter
Sponsored links

You don’t need to install any package to resize the column size of DataTable. However, DataTable and DataColumn don’t have such a property to update column size.

How can we implement it without any extra package?

This is the sample video.

You can find the completed code in my GitHub repo.

Sponsored links

Set a fixed width to DataTable widget

A scrollbar is necessary if the column width is bigger than the window width. So, let’s add the DataTable with Scrollbar.

I wanted to create an app for a Desktop, so the window size is changeable. To make the DataTable width match the window size, MediaQuery.of(context).size.width is set to the Container width.

import 'package:flutter/material.dart';

class TableColumnResize extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TableColumnResize();
}

class _TableColumnResize extends State<TableColumnResize> {
  final verticalScrollController = ScrollController();
  final horizontalScrollController = ScrollController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(
        title: Text("Table Column Resize"),
      ),
      body: Center(
        child: Container(
          width: MediaQuery.of(context).size.width, // match the window width
          height: 200,
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.black,
              width: 3,
            ),
          ),
          margin: const EdgeInsets.all(15.0),

          child: Scrollbar(
            thumbVisibility: true,
            trackVisibility: true, // make the scrollbar easy to see
            controller: verticalScrollController,
            child: Scrollbar(
              thumbVisibility: true,
              trackVisibility: true,
              controller: horizontalScrollController,
              notificationPredicate: (notif) => notif.depth == 1,
              child: SingleChildScrollView(
                controller: verticalScrollController,
                scrollDirection: Axis.vertical,
                child: SingleChildScrollView(
                  controller: horizontalScrollController,
                  scrollDirection: Axis.horizontal,
                  child: _resizableColumnWidth(), // DataTable creation
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

I recorded two videos with/without the setting so that you can easily choose your preference according to your needs.

Without width: MediaQuery.of(context).size.width,

With width: MediaQuery.of(context).size.width,

Check the following post too if you want to know about cross axis scroll deeper.

Sponsored links

Make the column resizable

Add Positioned and GestureDetector

As mentioned, DataTable and DataColumn don’t provide a property to hook column drag event. We somehow need to add GestureDetector to the DataColumn. To achieve this, we use Stack and Positioned. Multiple widgets can be used in the same area if we use Stack.

Let’s see the code.

Widget _resizableColumnWidth() {
  return DataTable(
      columns: [
        DataColumn(
          label: Stack(
            children: [
              Container(
                child: Text('Column 1'), // Header text
                width: columnWidth,
                constraints: BoxConstraints(minWidth: 100),
              ),
              Positioned(
                right: 0,
                child: GestureDetector(                  
                  child: Container(
                    width: 10,
                    height: 10,
                    decoration: BoxDecoration(
                      color: Colors.blue.withOpacity(1),
                      shape: BoxShape.circle,
                    ),
                  ),
                ),
              )
            ],
          ),
        ),
        DataColumn(label: Text("Column 2")),
        DataColumn(label: Text("Column 3")),
      ],
      rows: ...,
}

DataTable.columns requires DataColumn. We can’t set another widget. So, we use Stack in it. Then, we need to pile up multiple widgets there. Container is the main widget for the column. On the Container, put an additional widget that supports dragging. I’ve put a circle there.

The appearance looks like this.

Define drag start and dragging behavior

The next step is to add the behavior to onPanStart and onPanUpdate to GestureDetector.

double columnWidth = 200;
double initX = 0;

Widget _resizableColumnWidth() {
  return DataTable(
      columns: [
        DataColumn(
          label: Stack(
            children: [
              Container(...),
              Positioned(
                right: 0,
                child: GestureDetector(
                  onPanStart: (details) {
                    setState(() {
                      initX = details.globalPosition.dx;
                    });
                  },
                  onPanUpdate: (details) {
                    final increment = details.globalPosition.dx - initX;
                    final newWidth = columnWidth + increment;
                    setState(() {
                      initX = details.globalPosition.dx;
                      columnWidth = newWidth;
                    });
                  },
                  child: ...
                ),
              )
            ],
          ),
        ),
        DataColumn(label: Text("Column 2")),
        DataColumn(label: Text("Column 3")),
      ],
      rows: List.generate(
        20,
        (index) => DataRow(
          cells: [
            DataCell(
              ConstrainedBox(
                constraints: BoxConstraints(maxWidth: columnWidth),
                child: Text(...),
              ),
            ),
            DataCell(Text("Column2: Row index $index")),
            DataCell(Text("Column3: Row index $index")),
          ],
        ),
      ));
}

We need to calculate the column width when the circle is being dragged. When it’s tapped/clicked, we need to know the global position of X. Then, we can calculate how far the circle widget is moved from the base point X.

setState is called in onPanUpdate but if you want to update the column width only when the action ends, move setState to onPanEnd

columnWidth is used not only for DataColumn but also for DataCell in DataRow. If the DataCell doesn’t use ConstrainedBox, only header row width changes but not the data rows.

Set the minimum column width

You might not want to make the column invisible. Set your desired column size to columnWidth when the result becomes smaller than it.

final minimumColumnWidth = 50.0;


onPanUpdate: (details) {
  final increment = details.globalPosition.dx - initX;
  final newWidth = columnWidth + increment;
  setState(() {
      initX = details.globalPosition.dx;
      columnWidth = newWidth > minimumColumnWidth
          ? newWidth
          : minimumColumnWidth;
  });
},

Clipping DataRow text

Likewise, you might want to clip the data row text when the column width is smaller than the text length.

rows: List.generate(
  20,
  (index) => DataRow(
    cells: [
      DataCell(
        ConstrainedBox(
          constraints: BoxConstraints(maxWidth: columnWidth),
          child: Text(
            "Column1: Row index $index: long text 1234567890 1234567890 1234567890 1234567890",
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
            softWrap: false,
          ),
        ),
      ),
      DataCell(Text("Column2: Row index $index")),
      DataCell(Text("Column3: Row index $index")),
    ],
  ),
));

Without clipping

With clipping

Comments

Copied title and URL