buildHeaderColumns method
Builds header columns for the given headerRows and rowIndex.
parentMaxWidth is the available width for the header row.
Implementation
hr.HeaderRow buildHeaderColumns(
List<HeaderRow> headerRows,
int rowIndex, {
double? parentMaxWidth,
Map<int, double> widthOverrides = const <int, double>{},
}) {
final columnsDef = headerRows[rowIndex].columns;
// Calculate total fixed width and collect flex values
double totalFixedWidth = 0.0;
double totalFlex = 0;
List<double> flexValues = [];
for (final entry in columnsDef.asMap().entries) {
final col = entry.value;
final widthOverride = widthOverrides[entry.key];
if (widthOverride != null) {
totalFixedWidth += widthOverride;
flexValues.add(0);
} else if (col.width != null) {
totalFixedWidth += col.width!;
flexValues.add(0);
} else if (col.minimumWidth != null && col.flex == null) {
// treat minimum-only columns as fixed for layout purposes
totalFixedWidth += col.minimumWidth!;
flexValues.add(0);
} else if (col.flex != null) {
totalFlex += col.flex!;
flexValues.add(col.flex!);
}
}
// Subtract column separator width(s) from available width
final int separatorCount = (columnsDef.isNotEmpty) ? columnsDef.length : 0;
final double totalSeparatorWidth = separatorCount * kColumnSeparatorWidth;
// Calculate available width for flexible columns (never negative)
double availableWidth = max(0.0, (parentMaxWidth ?? 0) - totalFixedWidth - totalSeparatorWidth);
// Prepare assigned widths array
final int colCount = columnsDef.length;
List<double> assignedWidths = List<double>.filled(colCount, 0.0);
// Assign fixed / minimum-only columns immediately
for (int i = 0; i < colCount; i++) {
final col = columnsDef[i];
if (col.width != null) {
assignedWidths[i] = col.width!;
} else if (col.minimumWidth != null && col.flex == null) {
assignedWidths[i] = col.minimumWidth!;
}
}
// Distribute availableWidth among flexible columns with iterative clamping
final remainingIndices = <int>{};
for (int i = 0; i < colCount; i++) {
if (flexValues[i] > 0) remainingIndices.add(i);
}
double remainingWidth = availableWidth;
double remainingFlex = totalFlex;
// If there are no flexible columns, nothing to distribute
if (remainingIndices.isNotEmpty && remainingFlex > 0) {
// Iteratively clamp columns that hit min/max and redistribute
while (remainingIndices.isNotEmpty) {
bool anyClampedThisRound = false;
for (final idx in remainingIndices.toList()) {
final col = columnsDef[idx];
final double flex = flexValues[idx];
final double minW = col.minimumWidth ?? 50.0;
final double maxW = col.maximumWidth ?? double.infinity;
// Proposed width based on remaining flex share
final double proposed = remainingFlex > 0 ? remainingWidth * (flex / remainingFlex) : 0.0;
final double clamped = proposed.clamp(minW, maxW);
// If clamped to a bound, finalize this column and remove from remaining set
if (clamped == minW || clamped == maxW) {
assignedWidths[idx] = clamped;
remainingWidth -= clamped;
remainingFlex -= flex;
remainingIndices.remove(idx);
anyClampedThisRound = true;
}
}
if (!anyClampedThisRound) {
// No further clamping -> distribute remaining proportionally
for (final idx in remainingIndices) {
final col = columnsDef[idx];
final double flex = flexValues[idx];
final double minW = col.minimumWidth ?? 50.0;
final double maxW = col.maximumWidth ?? double.infinity;
final double w = remainingFlex > 0 ? (remainingWidth * (flex / remainingFlex)) : 0.0;
assignedWidths[idx] = w.clamp(minW, maxW);
}
break;
}
}
}
List<hc.HeaderColumn> columns = [];
for (int columnIndex = 0; columnIndex < columnsDef.length; columnIndex++) {
final headerCol = columnsDef[columnIndex];
bool pinLeft = headerCol.pinLeft;
bool pinRight = headerCol.pinRight;
double width;
if (widthOverrides[columnIndex] case final widthOverride?) {
width = widthOverride;
} else if (headerCol.width != null) {
width = headerCol.width!;
} else if (flexValues[columnIndex] > 0) {
// use the computed assigned width for flexible columns
width = assignedWidths[columnIndex] > 0 ? assignedWidths[columnIndex] : (headerCol.minimumWidth ?? 50);
} else {
double maxWidth = headerCol.maximumWidth ?? double.infinity;
width = (headerCol.minimumWidth ?? 50).clamp(0, maxWidth);
}
columns.add(
hc.HeaderColumn(
widget: _buildHeaderColumn(headerRows[rowIndex], columnIndex),
autoSizeWidget: headerCol.widget,
autoSize: headerCol.autoSize,
autoSizeWidth: headerCol.autoSizeWidth,
autoSizeWidthBuilder: headerCol.autoSizeWidthBuilder,
textVisibilityDialog: headerCol.textVisibilityDialog,
width: ValueNotifier(width),
minimumWidth: headerCol.minimumWidth ?? 50,
maximumWidth: headerCol.maximumWidth,
height: headerCol.height,
visible: ValueNotifier(headerCol.visible),
resizable: headerCol.resizable,
combinedColumns: headerCol.combinedColumns,
pinLeft: ValueNotifier(pinLeft),
pinRight: ValueNotifier(pinRight),
canHide: headerCol.canHide,
sortable: headerCol.sortable,
filterable: headerCol.filterable,
filterType: headerCol.filterType,
reorderable: headerCol.reorderable,
sortComparator: headerCol.sortComparator,
sourceColumnIndex: columnIndex,
),
);
}
return hr.HeaderRow(columns: columns);
}