Search code examples
flutterflutter-layoutflutter-datatable

Flutter color part of row in DataTable


Is it possible to only color a part of a row in a DataTable like 55% of the space?

Example

I am open to alternatives to the DataTable.

I tried it with DataRow color but that colors the complete row.


Solution

  • I found a solution by making a custom DataTable class with two new parameters and some other code changes. Please let me know if someone finds a better solution.

    Copy the code from original DataTable and add/replace the following code parts

    Here are the relevant code parts:

    class CustomDataTable extends StatelessWidget {
      CustomDataTable({
        Key? key,
        required this.columns,
        this.sortColumnIndex,
        this.sortAscending = true,
        this.onSelectAll,
        this.decoration,
        this.dataRowColor,
        this.dataRowHeight,
        this.dataTextStyle,
        this.headingRowColor,
        this.headingRowHeight,
        this.headingTextStyle,
        this.horizontalMargin,
        this.columnSpacing,
        this.showCheckboxColumn = true,
        this.showBottomBorder = false,
        this.dividerThickness,
        required this.rows,
        this.checkboxHorizontalMargin,
        this.border,
        this.dataRowPartIndicatorList,
        this.dataRowPartColorGradientList,
      }) : assert(columns.isNotEmpty),
           assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
           assert(!rows.any((DataRow row) => row.cells.length != columns.length)),
           assert(dividerThickness == null || dividerThickness >= 0),
           _onlyTextColumn = _initOnlyTextColumn(columns),
           super(key: key);
    
      /// List of rows. Indicates how much of the row should be colored.
      /// Values from 1 to 100.
      final List<int?>? dataRowPartIndicatorList;
    
      /// List of rows. The color is used to color part of the row.
      final List<Color?>? dataRowPartColorGradientList;
    
      @override
      Widget build(BuildContext context) {
        assert(!_debugInteractive || debugCheckHasMaterial(context));
    
        final ThemeData theme = Theme.of(context);
        final DataTableThemeData dataTableTheme = DataTableTheme.of(context);
        final MaterialStateProperty<Color?>? effectiveHeadingRowColor = headingRowColor
          ?? dataTableTheme.headingRowColor
          ?? theme.dataTableTheme.headingRowColor;
        final MaterialStateProperty<Color?>? effectiveDataRowColor = dataRowColor
          ?? dataTableTheme.dataRowColor
          ?? theme.dataTableTheme.dataRowColor;
        final MaterialStateProperty<Color?> defaultRowColor = MaterialStateProperty.resolveWith(
          (Set<MaterialState> states) {
            if (states.contains(MaterialState.selected)) {
              return theme.colorScheme.primary.withOpacity(0.08);
            }
            return null;
          },
        );
        final bool anyRowSelectable = rows.any((DataRow row) => row.onSelectChanged != null);
        final bool displayCheckboxColumn = showCheckboxColumn && anyRowSelectable;
        final Iterable<DataRow> rowsWithCheckbox = displayCheckboxColumn ?
          rows.where((DataRow row) => row.onSelectChanged != null) : <DataRow>[];
        final Iterable<DataRow> rowsChecked = rowsWithCheckbox.where((DataRow row) => row.selected);
        final bool allChecked = displayCheckboxColumn && rowsChecked.length == rowsWithCheckbox.length;
        final bool anyChecked = displayCheckboxColumn && rowsChecked.isNotEmpty;
        final bool someChecked = anyChecked && !allChecked;
        final double effectiveHorizontalMargin = horizontalMargin
          ?? dataTableTheme.horizontalMargin
          ?? theme.dataTableTheme.horizontalMargin
          ?? _horizontalMargin;
        final double effectiveCheckboxHorizontalMarginStart = checkboxHorizontalMargin
          ?? dataTableTheme.checkboxHorizontalMargin
          ?? theme.dataTableTheme.checkboxHorizontalMargin
          ?? effectiveHorizontalMargin;
        final double effectiveCheckboxHorizontalMarginEnd = checkboxHorizontalMargin
          ?? dataTableTheme.checkboxHorizontalMargin
          ?? theme.dataTableTheme.checkboxHorizontalMargin
          ?? effectiveHorizontalMargin / 2.0;
        final double effectiveColumnSpacing = columnSpacing
          ?? dataTableTheme.columnSpacing
          ?? theme.dataTableTheme.columnSpacing
          ?? _columnSpacing;
    
    
        final List<TableColumnWidth> tableColumns = List<TableColumnWidth>.filled(columns.length + (displayCheckboxColumn ? 1 : 0), const _NullTableColumnWidth());
        final List<TableRow> tableRows = List<TableRow>.generate(
          rows.length + 1, // the +1 is for the header row
          (int index) {
            final bool isSelected = index > 0 && rows[index - 1].selected;
            final bool isDisabled = index > 0 && anyRowSelectable && rows[index - 1].onSelectChanged == null;
            final Set<MaterialState> states = <MaterialState>{
              if (isSelected)
                MaterialState.selected,
              if (isDisabled)
                MaterialState.disabled,
            };
            final Color? resolvedDataRowColor = index > 0 ? (rows[index - 1].color ?? effectiveDataRowColor)?.resolve(states) : null;
            final Color? resolvedHeadingRowColor = effectiveHeadingRowColor?.resolve(<MaterialState>{});
            final Color? rowColor = index > 0 ? resolvedDataRowColor : resolvedHeadingRowColor;
            final BorderSide borderSide = Divider.createBorderSide(
              context,
              width: dividerThickness
                ?? dataTableTheme.dividerThickness
                ?? theme.dataTableTheme.dividerThickness
                ?? _dividerThickness,
            );
            final Border? border = showBottomBorder
              ? Border(bottom: borderSide)
              : index == 0 ? null : Border(top: borderSide);
            return TableRow(
              key: index == 0 ? _headingRowKey : rows[index - 1].key,
              decoration: BoxDecoration(
                border: border,
                color: rowColor ?? defaultRowColor.resolve(states),
                gradient: index != 0 && dataRowPartIndicatorList != null && dataRowPartColorGradientList != null ? LinearGradient(
                  colors: List.generate(100, (indexList) => indexList < dataRowPartIndicatorList![index - 1]! ? dataRowPartColorGradientList![index - 1]!.withOpacity(0.4) : Colors.transparent),
                  begin: Alignment.centerLeft,
                  stops: List.generate(100, (indexList) => indexList.toDouble() * 0.01),
                ) : null,
              ),
              children: List<Widget>.filled(tableColumns.length, const _NullWidget()),
            );
          },
        );
    
        int rowIndex;
    
        int displayColumnIndex = 0;
        if (displayCheckboxColumn) {
          tableColumns[0] = FixedColumnWidth(effectiveCheckboxHorizontalMarginStart + Checkbox.width + effectiveCheckboxHorizontalMarginEnd);
          tableRows[0].children![0] = _buildCheckbox(
            context: context,
            checked: someChecked ? null : allChecked,
            onRowTap: null,
            onCheckboxChanged: (bool? checked) => _handleSelectAll(checked, someChecked),
            overlayColor: null,
            tristate: true,
          );
          rowIndex = 1;
          for (final DataRow row in rows) {
            tableRows[rowIndex].children![0] = _buildCheckbox(
              context: context,
              checked: row.selected,
              onRowTap: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
              onCheckboxChanged: row.onSelectChanged,
              overlayColor: row.color ?? effectiveDataRowColor,
              tristate: false,
            );
            rowIndex += 1;
          }
          displayColumnIndex += 1;
        }
    
        for (int dataColumnIndex = 0; dataColumnIndex < columns.length; dataColumnIndex += 1) {
          final DataColumn column = columns[dataColumnIndex];
    
          final double paddingStart;
          if (dataColumnIndex == 0 && displayCheckboxColumn && checkboxHorizontalMargin != null) {
            paddingStart = effectiveHorizontalMargin;
          } else if (dataColumnIndex == 0 && displayCheckboxColumn) {
            paddingStart = effectiveHorizontalMargin / 2.0;
          } else if (dataColumnIndex == 0 && !displayCheckboxColumn) {
            paddingStart = effectiveHorizontalMargin;
          } else {
            paddingStart = effectiveColumnSpacing / 2.0;
          }
    
          final double paddingEnd;
          if (dataColumnIndex == columns.length - 1) {
            paddingEnd = effectiveHorizontalMargin;
          } else {
            paddingEnd = effectiveColumnSpacing / 2.0;
          }
    
          final EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(
            start: paddingStart,
            end: paddingEnd,
          );
          if (dataColumnIndex == _onlyTextColumn) {
            tableColumns[displayColumnIndex] = const IntrinsicColumnWidth(flex: 1.0);
          } else {
            tableColumns[displayColumnIndex] = const IntrinsicColumnWidth();
          }
          tableRows[0].children![displayColumnIndex] = _buildHeadingCell(
            context: context,
            padding: padding,
            label: column.label,
            tooltip: column.tooltip,
            numeric: column.numeric,
            onSort: column.onSort != null ? () => column.onSort!(dataColumnIndex, sortColumnIndex != dataColumnIndex || !sortAscending) : null,
            sorted: dataColumnIndex == sortColumnIndex,
            ascending: sortAscending,
            overlayColor: effectiveHeadingRowColor,
          );
          rowIndex = 1;
          for (final DataRow row in rows) {
            final DataCell cell = row.cells[dataColumnIndex];
            tableRows[rowIndex].children![displayColumnIndex] = _buildDataCell(
              context: context,
              padding: padding,
              label: cell.child,
              numeric: column.numeric,
              placeholder: cell.placeholder,
              showEditIcon: cell.showEditIcon,
              onTap: cell.onTap,
              onDoubleTap: cell.onDoubleTap,
              onLongPress: cell.onLongPress,
              onTapCancel: cell.onTapCancel,
              onTapDown: cell.onTapDown,
              onSelectChanged: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
              overlayColor: row.color ?? effectiveDataRowColor,
              onRowLongPress: row.onLongPress,
            );
            rowIndex += 1;
          }
          displayColumnIndex += 1;
        }
    
        return Container(
          decoration: decoration ?? dataTableTheme.decoration ?? theme.dataTableTheme.decoration,
          child: Material(
            type: MaterialType.transparency,
            child: Table(
              columnWidths: tableColumns.asMap(),
              children: tableRows,
              border: border,
            ),
          ),
        );
      }
    }