Search code examples
flutterdatatable

flutter-datatable : scrolling widgets out of screen in horizontal scroll-view


I am making custom dataTable with using row, column and SingleChildScrollView. here my requirement is header must be sticky and table rows must have bidirectional scrollable. I get vertical scrolling but while I scroll horizontally widgets move outside of screen infinitely. have any solution or suggest best plugin/Package for flutter web. (i am making admin panel with flutter web).

i need table like this.

enter image description here

 I tried to datatable,datatable2,PaginatedDataTable but problem is this table's row elements take same width and, i need row elements width as per content have it, and row element must have take a widget so that i can add Text,button or as per need in table. 

CODE

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class MyCustomDataTable extends StatelessWidget {
  const MyCustomDataTable({super.key});

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(),
      body: Container(
        height: size.height,
        width: size.width,
        decoration: BoxDecoration(
          border: Border.all(
            color: Colors.grey,
            width: 0.45,
          ),
        ),
        padding: const EdgeInsets.all(10),
        child: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          dragStartBehavior: DragStartBehavior.down,
          clipBehavior: Clip.none,
          keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: List.generate(
                  30,
                  (index) => Container(
                    width: 80,
                    height: 30,
                    margin: const EdgeInsets.only(right: 20),
                    color: Colors.purple,
                    child: Center(
                      child: Text("${1 + index}"),
                    ),
                  ),
                ).toList(),
              ),
              Row(
                children: List.generate(
                  30,
                  (index) => Container(
                    width: 80,
                    height: 30,
                    margin: const EdgeInsets.only(right: 20),
                    color: Colors.purple,
                    child: Center(
                      child: Text("${1 + index}"),
                    ),
                  ),
                ).toList(),
              ),
              const SizedBox(height: 20),
              Flexible(
                child: SizedBox(
                  width: double.maxFinite,
                  child: ListView.builder(
                    itemCount: 50,
                    shrinkWrap: true,
                    itemBuilder: (context, index) {
                      return Row(
                        children: List.generate(
                          30,
                          (index) => Container(
                            width: 80,
                            height: 30,
                            margin: const EdgeInsets.only(right: 20),
                            color: Colors.purple.withOpacity(index / 30),
                            child: Center(
                              child: Text("${1 + index}"),
                            ),
                          ),
                        ).toList(),
                      );
                    },
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}


Solution

  • I am getting same output as i want. for that i was used lazy_data_table

    actually I made lots of changes as per requred and then i achieve this result.And lots of changes i am doing continuously.

    Here i made LazyDataTable2 Class/Component base on LazyDataTable.

    And This LazyDataTable2 is giving me result as i want.

    Code

    class LazyDataTable2 extends StatefulWidget {
      final int rows, columns;
      final Widget Function(int, int) dataCellBuilder;
      final double topHeaderHeight, leftHeaderWidth, cellHeight, cellWidth;
      final double flexFactor;
      final Map<int, double> customCellWidth;
      final List<DataTableHeader> header;
      final Widget Function(int)? leftHeaderBuilder;
      final Widget? topLeftCornerWidget;
      LazyDataTable2({
        super.key,
        required this.rows,
        required this.columns,
        required this.dataCellBuilder,
        this.topHeaderHeight = 38,
        this.cellHeight = 33,
        this.cellWidth = 80,
        this.leftHeaderWidth = 00, // 100 is best value
        this.flexFactor = 0, //  0.625,
        this.customCellWidth = const <int, double>{},
        required this.header,
        this.leftHeaderBuilder,
        this.topLeftCornerWidget,
      })  : assert(
              (flexFactor == 0 && leftHeaderWidth == 0) ||
                  flexFactor != 0 && leftHeaderWidth != 0,
              "flexFactor & leftHeaderWidth both have zero value on not zero value: ErrorMsg",
            ),
            assert(
              columns != 0,
              "number of columns can't be zero so, columns!=0 this is not true here : ErrorMsg ",
            ),
            assert(
              header.isNotEmpty && header.length == columns,
              "header must not empty and header have same length as number of column : ErrorMsg",
            ),
            assert(
              header.every(
                (element) {
                  final bool res =
                      (element.header != null && element.headerText == null) ||
                          (element.header == null && element.headerText != null);
                  return res;
                },
              ),
              "for each and every element of header you can used either header as widget or headerText as String,only one of them : ErrorMsg",
            );
    
      @override
      State<LazyDataTable2> createState() => _LazyDataTable2State();
    }
    
    class _LazyDataTable2State extends State<LazyDataTable2> {
      final List<double> _flex = <double>[];
      final List<double> _minRowWidth = <double>[];
      @override
      void initState() {
        WidgetsBinding.instance.addPostFrameCallback(
          (_) {
        List.generate(
              widget.header.length,
              (index) {
                _flex.add(widget.header[index].flex); // add a flex
                _minRowWidth.add(
                  widget.header[index].minWidth,
                ); // add a min rowWidth
              },
            );
            if (mounted) setState(() {});
            printingWork();
          },
        );
    
        super.initState();
      }
    
      void printingWork() {
        debugPrint('flex => $_flex');
    
        _minRowWidth.forEach((element) {
          final i = _minRowWidth.indexOf(element);
          debugPrint('__minRowWidth[$i] => $element');
        });
      }
    
      final BoxBorder _boxBorder = Border.all(
        color: Colors.transparent,
        width: 0.225,
      );
    
      @override
      Widget build(BuildContext context) {
        final size = MediaQuery.of(context).size;
    
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(5.0),
                border: Border.all(
                  color: grey400,
                  width: 0.45,
                ),
              ),
              child: LayoutBuilder(
                builder: (context, constraints) {
                  final maxWidth = constraints.maxWidth;
                  final equal = maxWidth / (widget.columns);
    
                  /// sum of total flex => sigmaFlex
                  final sigmaFlex =
                      _flex.reduce((previous, next) => previous + next);
    
                  final List<int> keys =
                      List.generate(_flex.length, (index) => index);
    
                  final List<double> values = List.generate(
                    _flex.length,
                    (index) => max(
                      (equal * widget.columns) * (_flex[index] / sigmaFlex),
                      _minRowWidth[index],
                    ),
                  );
    
                  final customCellWidth =
                      Map<int, double>.fromIterables(keys, values);
    
                  debugPrint('sigmaFlex => $sigmaFlex');
                  debugPrint('equal => $equal');
                  debugPrint('values => $values');
                  debugPrint('customCellWidth => $customCellWidth');
                  debugPrint('maxWidth => $maxWidth :  Size =>  $size');
    
                  //  assigned width
                  _flex.forEach((element) {
                    final w = (equal * widget.columns) * (element / sigmaFlex);
                    final int index = _flex.indexOf(element);
                    debugPrint(
                        'rowWidth => calculated : $w  =>  Used[$index] : ${max(w, _minRowWidth[index])}');
                  });
                  debugPrint(
                    'calculatedWidth : ${values.reduce((value, element) => value + element)}',
                  );
    
                  return ConstrainedBox(
                    constraints: BoxConstraints(
                      maxHeight: (widget.cellHeight) * (widget.rows) +
                          widget.topHeaderHeight +
                          1.2,
                      maxWidth: maxWidth,
                      minHeight: 0.0,
                      minWidth: 0.0,
                    ),
                    child: LazyDataTable(
                      tableTheme: LazyDataTableTheme(
                        columnHeaderColor: primaryPallet5,
                        columnHeaderBorder: const Border(
                          left: BorderSide(
                            color: grey400,
                            width: 0.45,
                          ),
                          bottom: BorderSide(
                            color: grey400,
                            width: 0.45,
                          ),
                        ),
                        cellBorder: const Border(
                          left: BorderSide(
                            color: grey400,
                            width: 0.45,
                          ),
                        ),
                        rowHeaderBorder: _boxBorder,
                        cornerColor: primaryPallet5,
                        cornerBorder: const Border(
                          bottom: BorderSide(
                            color: grey400,
                            width: 0.45,
                          ),
                        ),
                      ),
                      rows: widget.rows,
                      columns: widget.columns,
                      tableDimensions: LazyDataTableDimensions(
                        cellHeight: widget.cellHeight,
                        cellWidth: widget.cellWidth,
                        customCellHeight: {},
                        customCellWidth: widget.customCellWidth.isNotEmpty
                            ? widget.customCellWidth
                            : customCellWidth,
                        topHeaderHeight: widget.topHeaderHeight,
                        leftHeaderWidth: widget.leftHeaderWidth,
                        bottomHeaderHeight: 0.0,
                        rightHeaderWidth: 0.0,
                      ),
                      dataCellBuilder: widget.dataCellBuilder,
                      topLeftCornerWidget: widget.topLeftCornerWidget,
                      topHeaderBuilder: (i) {
                        if (widget.header[i].header != null) {
                          return SizedBox(
                            child: widget.header[i].header,
                          );
                        } else {
                          return Padding(
                            padding: const EdgeInsets.only(
                              left: 15,
                              top: 8,
                              right: 15,
                            ),
                            child: Text(
                              "${widget.header[i].headerText}",
                              style: MyTextStyles.medium.copyWith(
                                color: appPrimary,
                                fontSize: 14.5,
                              ),
                            ),
                          );
                        }
                      },
                      leftHeaderBuilder: widget.leftHeaderBuilder,
                    ),
                  );
                },
              ),
            ),
    
            /// *page controller
            /// !Future Reference
          ],
        );
      }
    }
    
    class DataTableHeader {
      final String? headerText;
      final Widget? header;
      final double minWidth, flex;
    
      DataTableHeader({
        this.headerText,
        this.header,
        this.minWidth = 100, // set default value
        this.flex = 1, // set default value
      });
    }
    
    

    Sample Example

    class CustomerNotAgree extends StatelessWidget {
      const CustomerNotAgree({super.key});
    
      final List<Map<String, dynamic>> rows = const [
        {
          'title': 'Battery',
          'date': '29/04/2023',
        },
        {
          'title': 'Tire',
          'date': '29/04/2023',
        },
        {
          'title': 'Head Lights',
          'date': '29/04/2023',
        },
        {
          'title': 'Water Albo Upper',
          'date': '29/04/2023',
        },
        {
          'title': 'Air Filter ',
          'date': '29/04/2023',
        },
        {
          'title': 'Oil Filter',
          'date': '29/04/2023',
        },
        {
          'title': 'Engine Oil',
          'date': '29/04/2023',
        },
      ];
    
      @override
      Widget build(BuildContext context) {
        return ScreenLayout(
          isSecondaryAppBar: true,
          child: Padding(
            padding: Responsive.isDesktop(context)
                ? desktopPadding
                : Responsive.isTablet(context)
                    ? tabletPadding
                    : mobilePadding,
            child: LazyDataTable2(
              rows: rows.length,
              columns: 3,
              header: [
                DataTableHeader(
                  flex: 0.5,
                  minWidth: 60,
                  headerText: 'No.',
                ),
                DataTableHeader(
                  flex: 4.5,
                  minWidth: 180,
                  headerText: 'Parts / Service Name',
                ),
                DataTableHeader(
                  flex: 4,
                  minWidth: 100,
                  headerText: 'Date',
                ),
              ],
              dataCellBuilder: (r, c) {
                final srNo = r;
                if (c == 0) {
                  return Padding(
                    padding: tableCellPadding,
                    child: Text(
                      '${srNo + 1}',
                      style: tableFirstColumnStyle,
                    ),
                  );
                } else if (c == 1) {
                  return Padding(
                    padding: tableCellPadding,
                    child: Text(
                      rows[srNo]['title'],
                      style: tableColumnStyle,
                    ),
                  );
                } else {
                  return Padding(
                    padding: tableCellPadding,
                    child: Text(
                      rows[srNo]['date'],
                      style: tableColumnStyle,
                    ),
                  );
                }
              },
            ),
          ),
        );
      }
    }
    
    

    Result of my latest Example (From my flutter-web work) enter image description here