Table columns are resizable (#9485)

diff --git a/packages/devtools_app/lib/src/shared/table/_flat_table.dart b/packages/devtools_app/lib/src/shared/table/_flat_table.dart
index 087148c..79bb3e7 100644
--- a/packages/devtools_app/lib/src/shared/table/_flat_table.dart
+++ b/packages/devtools_app/lib/src/shared/table/_flat_table.dart
@@ -305,7 +305,7 @@
       fillWithEmptyRows: widget.fillWithEmptyRows,
       enableHoverHandling: widget.enableHoverHandling,
     );
-    if (widget.sizeColumnsToFit || tableController.columnWidths == null) {
+    if (tableController.columnWidths == null) {
       return LayoutBuilder(
         builder: (context, constraints) => buildTable(
           tableController.computeColumnWidthsSizeToFit(constraints.maxWidth),
diff --git a/packages/devtools_app/lib/src/shared/table/_table_row.dart b/packages/devtools_app/lib/src/shared/table/_table_row.dart
index 9161ca1..b7c6a01 100644
--- a/packages/devtools_app/lib/src/shared/table/_table_row.dart
+++ b/packages/devtools_app/lib/src/shared/table/_table_row.dart
@@ -37,6 +37,7 @@
        sortDirection = null,
        secondarySortColumn = null,
        onSortChanged = null,
+       onColumnResize = null,
        _rowType = _TableRowType.data,
        tall = false;
 
@@ -58,6 +59,7 @@
        sortDirection = null,
        secondarySortColumn = null,
        onSortChanged = null,
+       onColumnResize = null,
        searchMatchesNotifier = null,
        activeSearchMatchNotifier = null,
        tall = false,
@@ -75,6 +77,7 @@
     required this.sortColumn,
     required this.sortDirection,
     required this.onSortChanged,
+    this.onColumnResize,
     this.secondarySortColumn,
     this.onPressed,
     this.tall = false,
@@ -100,6 +103,7 @@
     required this.sortColumn,
     required this.sortDirection,
     required this.onSortChanged,
+    this.onColumnResize,
     this.secondarySortColumn,
     this.onPressed,
     this.tall = false,
@@ -176,6 +180,8 @@
   })?
   onSortChanged;
 
+  final void Function(int, double)? onColumnResize;
+
   final ValueListenable<List<T>>? searchMatchesNotifier;
 
   final ValueListenable<T?>? activeSearchMatchNotifier;
@@ -527,9 +533,32 @@
                 widget.columnWidths[index],
               );
             case _TableRowPartDisplayType.columnSpacer:
-              return const SizedBox(
-                width: columnSpacing,
-                child: VerticalDivider(width: columnSpacing),
+              final columnIndex = columnIndexMap[i - 1];
+              final onColumnResize = widget.onColumnResize;
+              final isResizable = columnIndex != null && onColumnResize != null;
+              return MouseRegion(
+                cursor: isResizable
+                    ? SystemMouseCursors.resizeColumn
+                    : SystemMouseCursors.basic,
+                child: GestureDetector(
+                  behavior: HitTestBehavior.opaque,
+                  onHorizontalDragUpdate: (details) {
+                    if (isResizable) {
+                      setState(() {
+                        final newWidth = _calculateNewColumnWidth(
+                          width: widget.columnWidths[columnIndex],
+                          delta: details.delta.dx,
+                          minWidth: widget.columns[columnIndex].minWidthPx,
+                        );
+                        onColumnResize(columnIndex, newWidth);
+                      });
+                    }
+                  },
+                  child: const SizedBox(
+                    width: columnSpacing,
+                    child: VerticalDivider(width: columnSpacing),
+                  ),
+                ),
               );
             case _TableRowPartDisplayType.columnGroupSpacer:
               return const _ColumnGroupSpacer();
@@ -560,4 +589,13 @@
 
   @override
   bool shouldShow() => widget.isShown;
+
+  double _calculateNewColumnWidth({
+    required double width,
+    required double delta,
+    double? minWidth,
+  }) => (width + delta).clamp(
+    minWidth ?? DevToolsTable.columnMinWidth,
+    double.infinity,
+  );
 }
diff --git a/packages/devtools_app/lib/src/shared/table/table.dart b/packages/devtools_app/lib/src/shared/table/table.dart
index a7a1ce4..9e01cf2 100644
--- a/packages/devtools_app/lib/src/shared/table/table.dart
+++ b/packages/devtools_app/lib/src/shared/table/table.dart
@@ -104,6 +104,8 @@
   final bool fillWithEmptyRows;
   final bool enableHoverHandling;
 
+  static const columnMinWidth = 50.0;
+
   @override
   DevToolsTableState<T> createState() => DevToolsTableState<T>();
 }
@@ -111,20 +113,22 @@
 @visibleForTesting
 class DevToolsTableState<T> extends State<DevToolsTable<T>>
     with AutoDisposeMixin {
+  static const _resizingDebounceDuration = Duration(milliseconds: 200);
+
   late ScrollController scrollController;
   late ScrollController pinnedScrollController;
   late ScrollController _horizontalScrollbarController;
 
   late List<T> _data;
 
-  /// An adjusted copy of `widget.columnWidths` where any variable width columns
-  /// may be increased so that the sum of all column widths equals the available
-  /// screen space.
-  ///
-  /// This must be calculated where we have access to the Flutter view
-  /// constraints (e.g. the [LayoutBuilder] below).
+  late Debouncer _resizingDebouncer;
+
   @visibleForTesting
-  late List<double> adjustedColumnWidths;
+  List<double> get columnWidths => _columnWidths;
+
+  late List<double> _columnWidths;
+
+  double? _previousViewWidth;
 
   @override
   void initState() {
@@ -132,6 +136,8 @@
 
     _initDataAndAddListeners();
 
+    _resizingDebouncer = Debouncer(duration: _resizingDebounceDuration);
+
     final initialScrollOffset = widget.preserveVerticalScrollPosition
         ? widget.tableController.tableUiState.scrollOffset
         : 0.0;
@@ -148,7 +154,7 @@
 
     pinnedScrollController = ScrollController();
 
-    adjustedColumnWidths = List.of(widget.columnWidths);
+    _columnWidths = List.of(widget.columnWidths);
   }
 
   @override
@@ -166,7 +172,9 @@
       _initDataAndAddListeners();
     }
 
-    adjustedColumnWidths = List.of(widget.columnWidths);
+    if (!collectionEquals(widget.columnWidths, oldWidget.columnWidths)) {
+      _columnWidths = List.of(widget.columnWidths);
+    }
   }
 
   void _initDataAndAddListeners() {
@@ -252,8 +260,14 @@
     super.dispose();
   }
 
+  void _handleColumnResize(int columnIndex, double newWidth) {
+    setState(() {
+      _columnWidths[columnIndex] = newWidth;
+    });
+  }
+
   /// The width of all columns in the table with additional padding.
-  double get _tableWidthForOriginalColumns {
+  double get _currentTableWidth {
     var tableWidth = 2 * defaultSpacing;
     final numColumnGroupSpacers =
         widget.tableController.columnGroups?.numSpacers ?? 0;
@@ -261,84 +275,76 @@
         widget.tableController.columns.numSpacers - numColumnGroupSpacers;
     tableWidth += numColumnSpacers * columnSpacing;
     tableWidth += numColumnGroupSpacers * columnGroupSpacingWithPadding;
-    for (final columnWidth in widget.columnWidths) {
+    for (final columnWidth in _columnWidths) {
       tableWidth += columnWidth;
     }
     return tableWidth;
   }
 
-  /// Modifies [adjustedColumnWidths] so that any available view space greater
-  /// than [_tableWidthForOriginalColumns] is distributed evenly across variable
-  /// width columns.
+  /// Adjusts the column widths to fit the new [viewWidth].
+  ///
+  /// This method will attempt to distribute any extra space (positive or
+  /// negative) amongst the variable-width columns. If there are no
+  /// variable-width columns, it will distribute the space amongst all columns.
   void _adjustColumnWidthsForViewSize(double viewWidth) {
-    final extraSpace = viewWidth - _tableWidthForOriginalColumns;
-    if (extraSpace <= 0) {
-      adjustedColumnWidths = List.of(widget.columnWidths);
+    final extraSpace = _currentTableWidth - viewWidth;
+    if (extraSpace == 0) {
       return;
     }
 
-    final adjustedColumnWidthsByIndex = <int, double>{};
-
-    /// Helper method to evenly distribute [space] among the columns at
-    /// [columnIndices].
-    ///
-    /// This method stores the adjusted width values in
-    /// [adjustedColumnWidthsByIndex].
-    void evenlyDistributeColumnSizes(List<int> columnIndices, double space) {
-      final targetSize = space / columnIndices.length;
-
-      var largestColumnIndex = -1;
-      var largestColumnWidth = 0.0;
-      for (final index in columnIndices) {
-        final columnWidth = widget.columnWidths[index];
-        if (columnWidth >= largestColumnWidth) {
-          largestColumnIndex = index;
-          largestColumnWidth = columnWidth;
-        }
-      }
-      if (targetSize < largestColumnWidth) {
-        // We do not have enough extra space to evenly distribute to all
-        // columns. Remove the largest column and recurse.
-        adjustedColumnWidthsByIndex[largestColumnIndex] = largestColumnWidth;
-        final newColumnIndices = List.of(columnIndices)
-          ..remove(largestColumnIndex);
-        return evenlyDistributeColumnSizes(
-          newColumnIndices,
-          space - largestColumnWidth,
-        );
-      }
-
-      for (int i = 0; i < columnIndices.length; i++) {
-        final columnIndex = columnIndices[i];
-        adjustedColumnWidthsByIndex[columnIndex] = targetSize;
-      }
-    }
-
-    final variableWidthColumnIndices = <int>[];
-    var sumVariableWidthColumnSizes = 0.0;
+    final variableWidthColumnIndices = <(int, double)>[];
     for (int i = 0; i < widget.tableController.columns.length; i++) {
       final column = widget.tableController.columns[i];
       if (column.fixedWidthPx == null) {
-        variableWidthColumnIndices.add(i);
-        sumVariableWidthColumnSizes += widget.columnWidths[i];
+        variableWidthColumnIndices.add((i, _columnWidths[i]));
       }
     }
-    final totalVariableWidthColumnSpace =
-        sumVariableWidthColumnSizes + extraSpace;
 
-    evenlyDistributeColumnSizes(
-      variableWidthColumnIndices,
-      totalVariableWidthColumnSpace,
+    // If the table contains variable width columns, then distribute the extra
+    // space between them. Otherwise, distribute the extra space between all the
+    // columns.
+    _distributeExtraSpace(
+      extraSpace,
+      indexedColumns: variableWidthColumnIndices.isNotEmpty
+          ? variableWidthColumnIndices
+          : _columnWidths.indexed,
     );
+  }
 
-    adjustedColumnWidths.clear();
-    for (int i = 0; i < widget.columnWidths.length; i++) {
-      final originalWidth = widget.columnWidths[i];
-      final isVariableWidthColumn = variableWidthColumnIndices.contains(i);
-      adjustedColumnWidths.add(
-        isVariableWidthColumn ? adjustedColumnWidthsByIndex[i]! : originalWidth,
+  /// Distributes [extraSpace] evenly between the given [indexedColumns].
+  ///
+  /// The [extraSpace] will be subtracted from each column's width. The
+  /// remainder of the division is subtracted from the last column to ensure a
+  /// perfect fit.
+  ///
+  /// This method respects the `minWidthPx` of each column.
+  void _distributeExtraSpace(
+    double extraSpace, {
+    required Iterable<(int, double)> indexedColumns,
+  }) {
+    final newWidths = List.of(_columnWidths);
+    final delta = extraSpace / indexedColumns.length;
+    final remainder = extraSpace % indexedColumns.length;
+
+    for (var i = 0; i < indexedColumns.length; i++) {
+      final columnIndex = indexedColumns.elementAt(i).$1;
+      var newWidth = indexedColumns.elementAt(i).$2;
+
+      newWidth -= delta;
+      if (i == indexedColumns.length - 1) {
+        newWidth -= remainder;
+      }
+
+      final column = widget.tableController.columns[columnIndex];
+      newWidths[columnIndex] = max(
+        newWidth,
+        column.minWidthPx ?? DevToolsTable.columnMinWidth,
       );
     }
+
+    setState(() {
+      _columnWidths = newWidths;
+    });
   }
 
   double _pinnedDataHeight(BoxConstraints tableConstraints) => min(
@@ -372,7 +378,7 @@
     return widget.rowBuilder(
       context: context,
       index: index,
-      columnWidths: adjustedColumnWidths,
+      columnWidths: _columnWidths,
       isPinned: isPinned,
       enableHoverHandling: widget.enableHoverHandling,
     );
@@ -406,7 +412,12 @@
     return LayoutBuilder(
       builder: (context, constraints) {
         final viewWidth = constraints.maxWidth;
-        _adjustColumnWidthsForViewSize(viewWidth);
+        if (_previousViewWidth != null && viewWidth != _previousViewWidth) {
+          _resizingDebouncer.run(
+            () => _adjustColumnWidthsForViewSize(viewWidth),
+          );
+        }
+        _previousViewWidth = viewWidth;
         return Scrollbar(
           controller: _horizontalScrollbarController,
           thumbVisibility: true,
@@ -415,14 +426,15 @@
             controller: _horizontalScrollbarController,
             child: SelectionArea(
               child: SizedBox(
-                width: max(viewWidth, _tableWidthForOriginalColumns),
+                width: max(viewWidth, _currentTableWidth),
                 child: Column(
                   crossAxisAlignment: CrossAxisAlignment.stretch,
                   children: [
                     if (showColumnGroupHeader)
                       TableRow<T>.tableColumnGroupHeader(
                         columnGroups: columnGroups,
-                        columnWidths: adjustedColumnWidths,
+                        columnWidths: _columnWidths,
+                        onColumnResize: _handleColumnResize,
                         sortColumn: sortColumn,
                         sortDirection: tableUiState.sortDirection,
                         secondarySortColumn:
@@ -436,7 +448,8 @@
                       key: const Key('Table header'),
                       columns: widget.tableController.columns,
                       columnGroups: columnGroups,
-                      columnWidths: adjustedColumnWidths,
+                      columnWidths: _columnWidths,
+                      onColumnResize: _handleColumnResize,
                       sortColumn: sortColumn,
                       sortDirection: tableUiState.sortDirection,
                       secondarySortColumn:
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
index 82bb44a..4a7c3f5 100644
--- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
+++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -17,6 +17,8 @@
 
 - Added a horizontal scrollbar to data tables to help with navigation.  -
   [#9482](https://github.com/flutter/devtools/pull/9482)
+- Made it possible to resize data table columns by dragging the header separators.  -
+  [#9845](https://github.com/flutter/devtools/pull/9485)
 
 ## Inspector updates
 
diff --git a/packages/devtools_app/test/shared/table/table_test.dart b/packages/devtools_app/test/shared/table/table_test.dart
index b2303f4..dd2dcd9 100644
--- a/packages/devtools_app/test/shared/table/table_test.dart
+++ b/packages/devtools_app/test/shared/table/table_test.dart
@@ -411,7 +411,6 @@
         expect(data[8].name, 'Snap');
       }
     });
-
     // TODO(jacobr): add a golden image tests for column width tests.
     testWidgets('displays with many columns', (WidgetTester tester) async {
       final table = FlatTable<TestData>(
@@ -464,6 +463,7 @@
             ),
           ),
         );
+        await _waitForResizingDebouncer(tester);
       }
 
       group('size to fit', () {
@@ -484,7 +484,7 @@
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 3);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 400.0); // Fixed width column.
@@ -495,15 +495,14 @@
             expect(adjustedColumnWidths[1], 400.0);
             expect(adjustedColumnWidths[2], 3252.0);
           }
-
           viewSize.value = const Size(800.0, 200.0);
-          await tester.pumpAndSettle();
+          await _waitForResizingDebouncer(tester);
           {
             final DevToolsTableState<TestData> tableState = tester.state(
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 3);
             expect(columnWidths[0], 300.0);
             expect(columnWidths[1], 400.0);
@@ -516,13 +515,13 @@
           }
 
           viewSize.value = const Size(200.0, 200.0);
-          await tester.pumpAndSettle();
+          await _waitForResizingDebouncer(tester);
           {
             final DevToolsTableState<TestData> tableState = tester.state(
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 3);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 400.0); // Fixed width column.
@@ -531,7 +530,7 @@
             expect(adjustedColumnWidths.length, 3);
             expect(adjustedColumnWidths[0], 300.0);
             expect(adjustedColumnWidths[1], 400.0);
-            expect(adjustedColumnWidths[2], 0.0);
+            expect(adjustedColumnWidths[2], 50.0);
           }
         });
 
@@ -557,7 +556,7 @@
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 4);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 1620.0); // Min width wide column
@@ -572,13 +571,13 @@
           }
 
           viewSize.value = const Size(1000.0, 200.0);
-          await tester.pumpAndSettle();
+          await _waitForResizingDebouncer(tester);
           {
             final DevToolsTableState<TestData> tableState = tester.state(
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 4);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 120.0); // Min width wide column
@@ -593,13 +592,13 @@
           }
 
           viewSize.value = const Size(200.0, 200.0);
-          await tester.pumpAndSettle();
+          await _waitForResizingDebouncer(tester);
           {
             final DevToolsTableState<TestData> tableState = tester.state(
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 4);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 100.0); // Min width wide column
@@ -610,7 +609,7 @@
             expect(adjustedColumnWidths[0], 300.0);
             expect(adjustedColumnWidths[1], 100.0);
             expect(adjustedColumnWidths[2], 400.0);
-            expect(adjustedColumnWidths[3], 0.0);
+            expect(adjustedColumnWidths[3], 50.0);
           }
         });
 
@@ -638,7 +637,7 @@
                 find.byType(DevToolsTable<TestData>),
               );
               final columnWidths = tableState.widget.columnWidths;
-              final adjustedColumnWidths = tableState.adjustedColumnWidths;
+              final adjustedColumnWidths = tableState.columnWidths;
               expect(columnWidths.length, 5);
               expect(columnWidths[0], 300.0); // Fixed width column
               expect(columnWidths[1], 1076.0); // Min width wide column
@@ -656,13 +655,13 @@
             }
 
             viewSize.value = const Size(1501.0, 200.0);
-            await tester.pumpAndSettle();
+            await _waitForResizingDebouncer(tester);
             {
               final DevToolsTableState<TestData> tableState = tester.state(
                 find.byType(DevToolsTable<TestData>),
               );
               final columnWidths = tableState.widget.columnWidths;
-              final adjustedColumnWidths = tableState.adjustedColumnWidths;
+              final adjustedColumnWidths = tableState.columnWidths;
               expect(columnWidths.length, 5);
               expect(columnWidths[0], 300.0); // Fixed width column
               expect(columnWidths[1], 243.0); // Min width wide column
@@ -680,13 +679,13 @@
             }
 
             viewSize.value = const Size(1200.0, 200.0);
-            await tester.pumpAndSettle();
+            await _waitForResizingDebouncer(tester);
             {
               final DevToolsTableState<TestData> tableState = tester.state(
                 find.byType(DevToolsTable<TestData>),
               );
               final columnWidths = tableState.widget.columnWidths;
-              final adjustedColumnWidths = tableState.adjustedColumnWidths;
+              final adjustedColumnWidths = tableState.columnWidths;
               expect(columnWidths.length, 5);
               expect(columnWidths[0], 300.0); // Fixed width column
               expect(columnWidths[1], 134.0); // Min width wide column
@@ -704,13 +703,13 @@
             }
 
             viewSize.value = const Size(1000.0, 200.0);
-            await tester.pumpAndSettle();
+            await _waitForResizingDebouncer(tester);
             {
               final DevToolsTableState<TestData> tableState = tester.state(
                 find.byType(DevToolsTable<TestData>),
               );
               final columnWidths = tableState.widget.columnWidths;
-              final adjustedColumnWidths = tableState.adjustedColumnWidths;
+              final adjustedColumnWidths = tableState.columnWidths;
               expect(columnWidths.length, 5);
               expect(columnWidths[0], 300.0); // Fixed width column
               expect(columnWidths[1], 100.0); // Min width wide column
@@ -724,7 +723,7 @@
               expect(adjustedColumnWidths[1], 100.0);
               expect(adjustedColumnWidths[2], 160.0);
               expect(adjustedColumnWidths[3], 400.0);
-              expect(adjustedColumnWidths[4], 0.0);
+              expect(adjustedColumnWidths[4], 50.0);
             }
           },
         );
@@ -748,7 +747,7 @@
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 3);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 400.0); // Fixed width column.
@@ -757,17 +756,17 @@
             expect(adjustedColumnWidths.length, 3);
             expect(adjustedColumnWidths[0], 300.0);
             expect(adjustedColumnWidths[1], 400.0);
-            expect(adjustedColumnWidths[2], 3252.0);
+            expect(adjustedColumnWidths[2], 1200.0);
           }
 
           viewSize.value = const Size(800.0, 200.0);
-          await tester.pumpAndSettle();
+          await _waitForResizingDebouncer(tester);
           {
             final DevToolsTableState<TestData> tableState = tester.state(
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 3);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 400.0); // Fixed width column.
@@ -776,17 +775,17 @@
             expect(adjustedColumnWidths.length, 3);
             expect(adjustedColumnWidths[0], 300.0);
             expect(adjustedColumnWidths[1], 400.0);
-            expect(adjustedColumnWidths[2], 1200.0);
+            expect(adjustedColumnWidths[2], 52.0);
           }
 
           viewSize.value = const Size(200.0, 200.0);
-          await tester.pumpAndSettle();
+          await _waitForResizingDebouncer(tester);
           {
             final DevToolsTableState<TestData> tableState = tester.state(
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 3);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 400.0); // Fixed width column.
@@ -795,7 +794,7 @@
             expect(adjustedColumnWidths.length, 3);
             expect(adjustedColumnWidths[0], 300.0);
             expect(adjustedColumnWidths[1], 400.0);
-            expect(adjustedColumnWidths[2], 1200.0);
+            expect(adjustedColumnWidths[2], 50.0);
           }
         });
 
@@ -821,7 +820,7 @@
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 4);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 100.0); // Min width wide column
@@ -830,19 +829,19 @@
 
             expect(adjustedColumnWidths.length, 4);
             expect(adjustedColumnWidths[0], 300.0);
-            expect(adjustedColumnWidths[1], 1620.0);
+            expect(adjustedColumnWidths[1], 100.0);
             expect(adjustedColumnWidths[2], 400.0);
-            expect(adjustedColumnWidths[3], 1620.0);
+            expect(adjustedColumnWidths[3], 1200.0);
           }
 
           viewSize.value = const Size(1000.0, 200.0);
-          await tester.pumpAndSettle();
+          await _waitForResizingDebouncer(tester);
           {
             final DevToolsTableState<TestData> tableState = tester.state(
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 4);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 100.0); // Min width wide column
@@ -853,17 +852,17 @@
             expect(adjustedColumnWidths[0], 300.0);
             expect(adjustedColumnWidths[1], 100.0);
             expect(adjustedColumnWidths[2], 400.0);
-            expect(adjustedColumnWidths[3], 1200.0);
+            expect(adjustedColumnWidths[3], 670.0);
           }
 
           viewSize.value = const Size(200.0, 200.0);
-          await tester.pumpAndSettle();
+          await _waitForResizingDebouncer(tester);
           {
             final DevToolsTableState<TestData> tableState = tester.state(
               find.byType(DevToolsTable<TestData>),
             );
             final columnWidths = tableState.widget.columnWidths;
-            final adjustedColumnWidths = tableState.adjustedColumnWidths;
+            final adjustedColumnWidths = tableState.columnWidths;
             expect(columnWidths.length, 4);
             expect(columnWidths[0], 300.0); // Fixed width column.
             expect(columnWidths[1], 100.0); // Min width wide column
@@ -874,7 +873,7 @@
             expect(adjustedColumnWidths[0], 300.0);
             expect(adjustedColumnWidths[1], 100.0);
             expect(adjustedColumnWidths[2], 400.0);
-            expect(adjustedColumnWidths[3], 1200.0);
+            expect(adjustedColumnWidths[3], 50.0);
           }
         });
 
@@ -902,7 +901,7 @@
                 find.byType(DevToolsTable<TestData>),
               );
               final columnWidths = tableState.widget.columnWidths;
-              final adjustedColumnWidths = tableState.adjustedColumnWidths;
+              final adjustedColumnWidths = tableState.columnWidths;
               expect(columnWidths.length, 5);
               expect(columnWidths[0], 300.0); // Fixed width column
               expect(columnWidths[1], 100.0); // Min width wide column
@@ -912,36 +911,127 @@
 
               expect(adjustedColumnWidths.length, 5);
               expect(adjustedColumnWidths[0], 300.0);
-              expect(adjustedColumnWidths[1], 1014.0);
-              expect(adjustedColumnWidths[2], 1014.0);
-              expect(adjustedColumnWidths[3], 400.0);
-              expect(adjustedColumnWidths[4], 1200.0);
-            }
-
-            viewSize.value = const Size(1200.0, 200.0);
-            await tester.pumpAndSettle();
-            {
-              final DevToolsTableState<TestData> tableState = tester.state(
-                find.byType(DevToolsTable<TestData>),
-              );
-              final columnWidths = tableState.widget.columnWidths;
-              final adjustedColumnWidths = tableState.adjustedColumnWidths;
-              expect(columnWidths[0], 300.0); // Fixed width column
-              expect(columnWidths[1], 100.0); // Min width wide column
-              expect(columnWidths[2], 160.0); // Very wide min width wide column
-              expect(columnWidths[3], 400.0); // Fixed width column.
-              expect(columnWidths[4], 1200.0); // Variable width wide column.
-
-              expect(adjustedColumnWidths.length, 5);
-              expect(adjustedColumnWidths[0], 300.0);
               expect(adjustedColumnWidths[1], 100.0);
               expect(adjustedColumnWidths[2], 160.0);
               expect(adjustedColumnWidths[3], 400.0);
               expect(adjustedColumnWidths[4], 1200.0);
             }
+
+            viewSize.value = const Size(1200.0, 200.0);
+            await _waitForResizingDebouncer(tester);
+            {
+              final DevToolsTableState<TestData> tableState = tester.state(
+                find.byType(DevToolsTable<TestData>),
+              );
+              final columnWidths = tableState.widget.columnWidths;
+              final adjustedColumnWidths = tableState.columnWidths;
+              expect(columnWidths[0], 300.0); // Fixed width column
+              expect(columnWidths[1], 100.0); // Min width wide column
+              expect(columnWidths[2], 160.0); // Very wide min width wide column
+              expect(columnWidths[3], 400.0); // Fixed width column.
+              expect(columnWidths[4], 1200.0); // Variable width wide column.
+
+              expect(adjustedColumnWidths.length, 5);
+              expect(adjustedColumnWidths[0], 300.0);
+              expect(adjustedColumnWidths[1], 100.0);
+              expect(adjustedColumnWidths[2], 160.0);
+              expect(adjustedColumnWidths[3], 400.0);
+              expect(adjustedColumnWidths[4], 856.0);
+            }
           },
         );
       });
+
+      group('resizing columns', () {
+        late FlatTable<TestData> table;
+
+        Finder getTableHeaderFinder() => find.byKey(const Key('Table header'));
+
+        Finder getResizerFinder() => find.descendant(
+          of: getTableHeaderFinder(),
+          matching: find.byWidgetPredicate(
+            (widget) =>
+                widget is GestureDetector &&
+                widget.child is SizedBox &&
+                (widget.child as SizedBox).width == columnSpacing,
+          ),
+        );
+
+        setUp(() {
+          table = FlatTable<TestData>(
+            columns: [flatNameColumn, _NumberColumn()],
+            data: flatData,
+            dataKey: 'test-data',
+            keyFactory: (d) => Key(d.name),
+            defaultSortColumn: flatNameColumn,
+            defaultSortDirection: SortDirection.ascending,
+          );
+        });
+
+        testWidgets('resize with right to left drag', (
+          WidgetTester tester,
+        ) async {
+          await tester.pumpWidget(wrap(table));
+
+          final DevToolsTableState<TestData> tableState = tester.state(
+            find.byType(DevToolsTable<TestData>),
+          );
+
+          final initialWidths = List.of(tableState.columnWidths);
+          expect(initialWidths, orderedEquals([300.0, 400.0]));
+
+          final resizerFinder = getResizerFinder();
+          expect(resizerFinder, findsOneWidget);
+
+          double dragX = 100.0;
+          const dragOffset = 20.0;
+          await tester.drag(resizerFinder, Offset(dragX, 0));
+          await _waitForResizingDebouncer(tester);
+
+          final widthsAfterDrag1 = List.of(tableState.columnWidths);
+          expect(widthsAfterDrag1[0], initialWidths[0] + dragX - dragOffset);
+          expect(widthsAfterDrag1[1], initialWidths[1]);
+
+          dragX = 50.0;
+          await tester.drag(resizerFinder, Offset(0 - dragX, 0));
+          await _waitForResizingDebouncer(tester);
+
+          final widthsAfterDrag2 = tableState.columnWidths;
+          expect(widthsAfterDrag2[0], widthsAfterDrag1[0] - dragX + dragOffset);
+        });
+
+        testWidgets('resize with left to right drag', (
+          WidgetTester tester,
+        ) async {
+          await tester.pumpWidget(wrap(table));
+
+          final DevToolsTableState<TestData> tableState = tester.state(
+            find.byType(DevToolsTable<TestData>),
+          );
+
+          final initialWidths = List.of(tableState.columnWidths);
+          expect(initialWidths, orderedEquals([300.0, 400.0]));
+
+          final resizerFinder = getResizerFinder();
+          expect(resizerFinder, findsOneWidget);
+
+          double dragX = 50.0;
+          const dragOffset = 20.0;
+          await tester.drag(resizerFinder, Offset(0 - dragX, 0));
+          await _waitForResizingDebouncer(tester);
+
+          final widthsAfterDrag1 = List.of(tableState.columnWidths);
+          expect(widthsAfterDrag1[0], initialWidths[0] - dragX + dragOffset);
+          expect(widthsAfterDrag1[1], initialWidths[1]);
+
+          dragX = 100.0;
+          await tester.drag(resizerFinder, Offset(dragX, 0));
+          await _waitForResizingDebouncer(tester);
+
+          final widthsAfterDrag2 = tableState.columnWidths;
+          expect(widthsAfterDrag2[0], widthsAfterDrag1[0] + dragX - dragOffset);
+        });
+      });
     });
 
     testWidgets('can select an item', (WidgetTester tester) async {
@@ -1669,6 +1759,14 @@
   });
 }
 
+const resizingDebounceDurationInMs = 200;
+
+Future<void> _waitForResizingDebouncer(WidgetTester tester) async {
+  await tester.pumpAndSettle(
+    const Duration(milliseconds: resizingDebounceDurationInMs * 2),
+  );
+}
+
 class TestData extends TreeNode<TestData> {
   TestData(this.name, this.number);