Search code examples
flutterdatatable

DataCell's height is not adjusted automatically based on content Text height in Flutter


I use DataTable in Flutter.

DataCell has long Text and cell's height is not adjusted.

DataTable is positioned in SingleScrollView.

So I do many things..

I used Wrap outside Text, give softWrap: true to Text,

And I also gave minHeight: double.infinity to DataTable..etc

But nothing resolves my issue.

   DataCell(
                                        Align(
                                          alignment: Alignment.centerLeft,
                                          child: rowData.secret
                                              ? Text(
                                                  "비밀 글",
                                                  style: TextStyle(
                                                    color: Colors.grey.shade300,
                                                  ),
                                                )
                                              : rowData.todayDiary.length > 20
                                                  ? Text(
                                                      description,
                                                      textAlign:
                                                          TextAlign.start,
                                                      style: const TextStyle(
                                                        fontSize: Sizes.size13,
                                                      ),
                                                    )
                                                  : Text(
                                                      rowData.todayDiary,
                                                      textAlign:
                                                          TextAlign.start,
                                                      style: const TextStyle(
                                                        fontSize: Sizes.size13,
                                                      ),
                                                    ),
                                        ),
                                        onTap: () {
                                          setState(() {
                                            expandUpdate = true;
                                            if (isExpanded) {
                                              expandMap.remove(index);
                                            } else {
                                              expandMap[index] = true;
                                            }
                                          });
                                        },
                                      ),

Only when I use Wrap widget outside of Text it shows all Text unless hiding it.

However height itself of DataCell is not expanded at all.

Since I use SingleScrollView, I can't calculate height manually by MediaQuery

Please help me..

my full data table code is below

 Center(
                      child: SizedBox(
                        width: size.width - 400,
                        child: LayoutBuilder(
                          builder: (context, constraints) {
                            final totalWidth = constraints.maxWidth - 360;
                            final dateWidth = totalWidth * 0.2;
                            final diaryWidth = totalWidth * 0.5;
                            final moodWidth = totalWidth * 0.2;
                            final secretWidth = totalWidth * 0.1;
                            return DataTable(
                              columns: [
                                DataColumn(
                                  label: SizedBox(
                                    width: dateWidth,
                                    child: const Text(
                                      "날짜",
                                      textAlign: TextAlign.start,
                                    ),
                                  ),
                                ),
                                DataColumn(
                                  label: SizedBox(
                                    width: diaryWidth,
                                    child: const Text(
                                      "일기",
                                      textAlign: TextAlign.start,
                                    ),
                                  ),
                                ),
                                DataColumn(
                                  label: SizedBox(
                                    width: moodWidth,
                                    child: const Text(
                                      "감정",
                                      textAlign: TextAlign.start,
                                    ),
                                  ),
                                ),
                                DataColumn(
                                  label: SizedBox(
                                    width: secretWidth,
                                    child: const Text(
                                      "비밀",
                                      textAlign: TextAlign.center,
                                    ),
                                  ),
                                ),
                              ],
                              rows: List.generate(
                                diaryModelList.length,
                                (index) {
                                  final rowData = diaryModelList[index];
                                  final isExpanded =
                                      expandMap.containsKey(index) &&
                                          expandMap[index] == true;
                                  final description = isExpanded
                                      ? rowData.todayDiary
                                      : rowData.todayDiary.length > 21
                                          ? rowData.todayDiary.substring(0, 21)
                                          : rowData.todayDiary;
                                  return DataRow(
                                    cells: [
                                      DataCell(
                                        Align(
                                          alignment: Alignment.centerLeft,
                                          child: Text(
                                            convertTimettampToString(
                                              rowData.timestamp,
                                            ),
                                            style: const TextStyle(
                                              fontSize: Sizes.size13,
                                            ),
                                          ),
                                        ),
                                      ),
                                      // DataCell(
                                      //   Align(
                                      //     alignment: Alignment.centerLeft,
                                      //     child: Text(
                                      //       rowData.todayDiary,
                                      //       style: const TextStyle(
                                      //         fontSize: Sizes.size13,
                                      //       ),
                                      //       softWrap: true,
                                      //     ),
                                      //   ),
                                      // ),
                                      DataCell(
                                        Align(
                                          alignment: Alignment.centerLeft,
                                          child: rowData.secret
                                              ? Text(
                                                  "비밀 글",
                                                  style: TextStyle(
                                                    color: Colors.grey.shade300,
                                                  ),
                                                )
                                              : rowData.todayDiary.length > 20
                                                  ? Text(
                                                      description,
                                                      textAlign:
                                                          TextAlign.start,
                                                      style: const TextStyle(
                                                        fontSize: Sizes.size13,
                                                      ),
                                                    )
                                                  : Text(
                                                      rowData.todayDiary,
                                                      textAlign:
                                                          TextAlign.start,
                                                      style: const TextStyle(
                                                        fontSize: Sizes.size13,
                                                      ),
                                                    ),
                                        ),
                                        onTap: () {
                                          setState(() {
                                            expandUpdate = true;
                                            if (isExpanded) {
                                              expandMap.remove(index);
                                            } else {
                                              expandMap[index] = true;
                                            }
                                          });
                                        },
                                      ),
                                      DataCell(
                                        Align(
                                          alignment: Alignment.centerLeft,
                                          child: Text(
                                            rowData.todayMood.description!,
                                            textAlign: TextAlign.center,
                                            style: const TextStyle(
                                              fontSize: Sizes.size13,
                                            ),
                                          ),
                                        ),
                                      ),
                                      DataCell(
                                        Align(
                                          alignment: Alignment.center,
                                          child: rowData.secret
                                              ? const Icon(Icons.check)
                                              : null,
                                        ),
                                      ),
                                    ],
                                  );
                                },
                              ),
                            );
                          },
                        ),
                      ),
                    ),

Solution

  • I'm not too familiar with DataTable, but from looking at its source code, there doesn't seem to be a way to have dynamic table row heights. Here is an excerpt from the DataTable._buildDataCell source (see comments):

    label = Container(
          padding: padding,
          /// Since this Container is constrained to [effectiveDataRowMaxHeight], it cannot
          /// be dynamically sized.
          constraints: BoxConstraints(
            minHeight: effectiveDataRowMinHeight,
            maxHeight: effectiveDataRowMaxHeight
          ),
          alignment: numeric ? Alignment.centerRight : AlignmentDirectional.centerStart,
          child: DefaultTextStyle(
            style: effectiveDataTextStyle.copyWith(
              color: placeholder ? effectiveDataTextStyle.color!.withOpacity(0.6) : null,
            ),
            child: DropdownButtonHideUnderline(child: label),
          ),
        );
    

    Here is the output I was able to achieve (code below):

    enter image description here

    You might have to create your own widget to handle your scenario. I will provide you with a starting point example below.

    class Example extends StatelessWidget {
      const Example({super.key});
    
      @override
      Widget build(BuildContext context) {
        /// Your model data.
        final elements = getElements();
    
        return SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
    
            /// This is the list of elements. We 'shrinkWrap' it to allow the
            /// [ListView] to take up the minimum amount of space.
            child: ListView.builder(
              shrinkWrap: true,
              itemCount: elements.length,
              itemBuilder: (context, index) {
                final element = elements[index];
    
                return Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    if (index == 0) const Divider(),
                    Row(
                      children: [
                        /// You can calculate or set a maximum width using
                        /// [ConstrainedBox]; I use [Expanded] for simplicity.
                        Expanded(
                          child: Align(
                            alignment: Alignment.centerLeft,
                            child: Text(element.title),
                          ),
                        ),
                        const SizedBox(width: 24.0),
                        /// Flex 3 to 1 ratio for the description.
                        Expanded(
                          flex: 3,
                          child: Expandable(
                            header: const Align(
                              alignment: Alignment.centerLeft,
                              child: Text("Head"),
                            ),
                            body: Text(element.description),
                          ),
                        ),
                      ],
                    ),
                    const Divider(),
                  ],
                );
              },
            ),
          ),
        );
      }
    }
    

    Expandable:

    class Expandable extends StatefulWidget {
      const Expandable({
        super.key,
        this.duration = DurationConstants.medium,
        this.curve = Curves.fastEaseInToSlowEaseOut,
        this.initiallyExpanded = true,
        this.backgroundColor,
        this.margin = EdgeInsets.zero,
        this.padding = EdgeInsets.zero,
        this.header,
        required this.body,
      });
    
      final Duration duration;
      final Curve curve;
    
      final Color? backgroundColor;
      final EdgeInsets margin;
      final EdgeInsets padding;
    
      final bool initiallyExpanded;
    
      final Widget? header;
      final Widget body;
    
      @override
      State<Expandable> createState() => ExpandableState();
    }
    
    class ExpandableState extends State<Expandable>
        with SingleTickerProviderStateMixin {
      late bool _isExpanded;
    
      late final AnimationController _animationController;
      late final CurvedAnimation _curvedAnimation;
    
      @override
      void initState() {
        super.initState();
        _animationController = AnimationController(
          vsync: this,
          duration: widget.duration,
        );
    
        _curvedAnimation = CurvedAnimation(
          parent: _animationController,
          curve: widget.curve,
          reverseCurve: widget.curve.flipped,
        );
    
        if (_isExpanded = widget.initiallyExpanded) {
          _animationController.value = 1.0;
        }
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        final theme = Theme.of(context);
    
        return Container(
          margin: widget.margin,
          padding: widget.padding,
          decoration: BoxDecoration(
            color: widget.backgroundColor,
            borderRadius: const BorderRadius.all(Radius.circular(6.0)),
          ),
          clipBehavior: Clip.antiAlias,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              /// Header.
              if (widget.header != null)
                GestureDetector(
                  onTap: toggle,
                  behavior: HitTestBehavior.opaque,
                  child: SizedBox(
                    height: 56.0,
                    child: Stack(
                      children: [
                        Positioned(
                          top: .0,
                          bottom: .0,
                          left: .0,
                          right: .0,
                          child: widget.header!,
                        ),
                        Positioned(
                          top: .0,
                          bottom: .0,
                          right: .0,
                          child: AnimatedBuilder(
                            animation: _curvedAnimation,
                            builder: (_, child) => Transform.rotate(
                              angle: _curvedAnimation.value * math.pi,
                              child: child,
                            ),
                            child: Icon(
                              Icons.keyboard_arrow_down,
                              color: theme.colorScheme.primary,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
    
              /// Body.
              SizeTransition(
                sizeFactor: _curvedAnimation,
                child: AnimatedSize(
                  duration: DurationConstants.short,
                  child: widget.body,
                ),
              ),
            ],
          ),
        );
      }
    
      Future<void> expand() async {
        if (_isExpanded) {
          return;
        }
    
        setState(() => _isExpanded = true);
        return _animationController.forward();
      }
    
      Future<void> collapse() async {
        if (_isExpanded) {
          setState(() => _isExpanded = false);
          return _animationController.reverse();
        }
      }
    
      Future<void> toggle() {
        if (_isExpanded) {
          return collapse();
        } else {
          return expand();
        }
      }
    }
    

    Possibly helpful widgets: