Written by: Tornike Kurdadze | Sun Jul 28 2024 Dart Extensions

1. SeparatedRow

As name suggests, this widget separates children with a separator widget. It is useful when you need to separate children with a divider or any other widget.

Flutter widget SeparatedRow

The implementation:

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

class SeparatedRow extends StatelessWidget {
  const SeparatedRow({
    required this.children,
    required this.separator,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.mainAxisSize = MainAxisSize.min,
    super.key,
  });

  final List<Widget> children;
  final Widget separator;
  final MainAxisAlignment mainAxisAlignment;
  final CrossAxisAlignment crossAxisAlignment;
  final MainAxisSize mainAxisSize;

  @override
  Widget build(BuildContext context) {
    final itemCount = max(0, children.length * 2 - 1);
    return Row(
      mainAxisSize: mainAxisSize,
      mainAxisAlignment: mainAxisAlignment,
      crossAxisAlignment: crossAxisAlignment,
      children: [for (int i = 0; i < itemCount; i += 1) i].map((index) {
        final itemIndex = index ~/ 2;
        return index.isEven ? children[itemIndex] : separator;
      }).toList(growable: false),
    );
  }
}

2. SeparatedColumn

You guessed it right, this widget is similar to SeparatedRow but for columns.

Flutter widget SeparatedColumn

The implementation:

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

class SeparatedColumn extends StatelessWidget {
  const SeparatedColumn({
    required this.children,
    required this.separator,
    this.mainAxisAlignment = MainAxisAlignment.start,
    this.crossAxisAlignment = CrossAxisAlignment.center,
    this.mainAxisSize = MainAxisSize.min,
    super.key,
  });

  final List<Widget> children;
  final Widget separator;
  final MainAxisAlignment mainAxisAlignment;
  final CrossAxisAlignment crossAxisAlignment;
  final MainAxisSize mainAxisSize;

  @override
  Widget build(BuildContext context) {
    final itemCount = max(0, children.length * 2 - 1);

    return Column(
      mainAxisSize: mainAxisSize,
      mainAxisAlignment: mainAxisAlignment,
      crossAxisAlignment: crossAxisAlignment,
      children: [for (int i = 0; i < itemCount; i += 1) i].map((index) {
        final itemIndex = index ~/ 2;
        return index.isEven ? children[itemIndex] : separator;
      }).toList(growable: false),
    );
  }
}

3. TableGrid

This widget is useful when you need to create a grid with a specific number of columns but without specific aspect ratio.

Flutter widget TableGrid

The implementation:

import 'package:flutter/material.dart';

class TableGrid extends StatelessWidget {
  const TableGrid({
    required this.children,
    this.crossAxisCount = 3,
    this.mainAxisSpacing = 4,
    this.crossAxisSpacing = 4,
    this.mainAxisAlignment = MainAxisAlignment.start,
    super.key,
  });
  final List<Widget> children;
  final int crossAxisCount;
  final double mainAxisSpacing;
  final double crossAxisSpacing;
  final MainAxisAlignment mainAxisAlignment;

  @override
  Widget build(BuildContext context) {
    var rowIndex = -1;
    var itemIndex = -1;
    final rowCount = (children.length / crossAxisCount).ceil();

    int columnCount() {
      final hasIncompleteRow = children.length % crossAxisCount != 0;
      final isLastRow = rowIndex == (children.length / crossAxisCount).floor();

      if (hasIncompleteRow && isLastRow) {
        return children.length % crossAxisCount;
      }
      return crossAxisCount;
    }

    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: List.filled(rowCount, 0).map((_) {
        ++rowIndex;
        final semanticRowList = Iterable<int>.generate(columnCount() * 2 - 1);

        return Padding(
          padding: EdgeInsets.only(top: rowIndex > 0 ? crossAxisSpacing : 0),
          child: Row(
            mainAxisAlignment: mainAxisAlignment,
            children: semanticRowList.map<Widget>((semanticRowIndex) {
              return semanticRowIndex.isOdd
                  ? SizedBox(width: mainAxisSpacing)
                  : children[++itemIndex];
            }).toList(),
          ),
        );
      }).toList(growable: false),
    );
  }
}

4. AnotherStateManagementWidget

Just kidding, we already have enough of those. 😄

5. Clickable

GestureDetector is a great widget for handling user interactions, but it doesn’t provide much much to the user. For example it doesn’t chage the cursor on Web, or does’t provide splash effect on mobile. So here is the solution, Clickable widget.

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

class Clickable extends StatelessWidget {
  const Clickable({
    required this.child,
    required this.onTap,
    this.margin = EdgeInsets.zero,
    this.padding = EdgeInsets.zero,
    this.borderRadius,
    this.decoration,
    super.key,
  });

  final EdgeInsetsGeometry margin;
  final EdgeInsetsGeometry padding;
  final Widget child;
  final BorderRadius? borderRadius;
  final VoidCallback? onTap;
  final BoxDecoration? decoration;

  @override
  Widget build(BuildContext context) {
    return ExcludeFocus(
      child: MaterialButton(
        padding: EdgeInsets.zero,
        onPressed: onTap != null
            ? () {
                HapticFeedback.selectionClick();
                onTap!.call();
              }
            : null,
        splashColor: Colors.white.withOpacity(.05),
        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
        shape: borderRadius != null ? RoundedRectangleBorder(borderRadius: borderRadius!) : null,
        minWidth: 0,
        child: child,
      ),
    );
  }
}

More coming soon…

Coffee makes me feel like I have my shit together 😀, so here you go 💙