Marcus Mondel
Marcus Mondel

Reputation: 1

GridView or Rows/Colums and how to stretch it out evenly

I want to try to make a roulette table layout in my app, where the user could also select the number fields. I already have my version on git, but I am not sure how I should finish this layout, so it is evenly spaced and stretched out. I have tried to do it with a GridView but I failed, so I tried it with Rows and Columns now.

Here is a github link: https://github.com/montziboy/roulette_trainer

Thank you in advance.

Upvotes: 0

Views: 258

Answers (1)

pskink
pskink

Reputation: 24720

for such complex layouts like "roulette table layout" CustomMultiChildLayout widget could be the most flexible solution, note that it supports both landscape & portrait modes

if you need "selectable" areas outside the number 1-36 (ids: 0, 37..48) you would need to add some additional InkWells / GestureDetectors

this is how it looks in portrait mode:

enter image description here

class RouletteLayout extends StatefulWidget {
  @override
  State<RouletteLayout> createState() => _RouletteLayoutState();
}

class _RouletteLayoutState extends State<RouletteLayout> {
  int selected = 0;

  static final decoration = BoxDecoration(
    border: Border.all(
      color: Colors.black87,
      width: 1,
    ),
  );

  static final labels = {
    40: '1st 12', 41: '2nd 12', 42: '3rd 12',
    43: '1-18', 44: 'even', 47: 'odd', 48: '19-36',
  };

  @override
  Widget build(BuildContext context) {
    final red = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36};

    final orientation = MediaQuery.of(context).orientation;
    return CustomMultiChildLayout(
      children: [
        LayoutId(
          id: 0,
          child: RouletteNumber(
            text: '0',
            color: Color(0xff006600),
            selected: false,
            orientation: orientation,
            decoration: decoration,
          ),
        ),
        for (int i = 1; i <= 36; i++)
          LayoutId(
            id: i,
            child: RouletteNumber(
              text: i.toString(),
              color: red.contains(i)? Color(0xffdd0000) : Colors.black,
              selected: selected == i,
              orientation: orientation,
              decoration: decoration,
              onTap: () => setState(() => selected = i),
            ),
          ),
        for (int i = 37; i <= 39; i++)
          LayoutId(
            id: i,
            child: Container(
              decoration: decoration,
              padding: EdgeInsets.all(4),
              child: RotatedBox(
                quarterTurns: orientation == Orientation.landscape? 3 : 0,
                child: FittedBox(child: Text('2 to 1')),
              ),
            ),
          ),
        for (int i = 40; i <= 48; i++)
          LayoutId(
            id: i,
            child: _build(i, orientation),
          ),
      ],
      delegate: RouletteDelegate(orientation),
    );
  }

  Widget _build(int i, Orientation orientation) {
    if (i == 45) {
      return Container(
        color: const Color(0xffdd0000),
      );
    }
    if (i == 46) {
      return Container(
        color: Colors.black,
      );
    }
    return Container(
      decoration: decoration,
      child: RotatedBox(
        quarterTurns: orientation == Orientation.landscape? 0 : 1,
        child: Padding(
          padding: const EdgeInsets.all(4),
          child: FittedBox(child: Text(labels[i]!)),
        ),
      ),
    );
  }
}

class RouletteDelegate extends MultiChildLayoutDelegate {
  static const rows = 14.0;
  static const cols = 8.0;

  RouletteDelegate(this.orientation);

  final Orientation orientation;

  @override
  void performLayout(ui.Size size) {
    Map<int, Rect> rects;
    Size inputSize;
    if (orientation == Orientation.portrait) {
      inputSize = const Size(cols, rows);
      rects = {
        0: const Rect.fromLTWH(2, 0, 6, 1),   // 0
        for (int i = 0; i < 36; i++)
          i + 1: Rect.fromLTWH(2 + 2 * (i % 3), 1 + (i ~/ 3).toDouble(), 2, 1),
        37: const Rect.fromLTWH(2, 13, 2, 1), // 2 to 1 #1
        38: const Rect.fromLTWH(4, 13, 2, 1), // 2 to 1 #2
        39: const Rect.fromLTWH(6, 13, 2, 1), // 2 to 1 #3
        40: const Rect.fromLTWH(1, 1, 1, 4),  // 1st 12
        41: const Rect.fromLTWH(1, 5, 1, 4),  // 2nd 12
        42: const Rect.fromLTWH(1, 9, 1, 4),  // 3rd 12
        43: const Rect.fromLTWH(0, 1, 1, 2),  // 1-18
        44: const Rect.fromLTWH(0, 3, 1, 2),  // even
        45: const Rect.fromLTWH(0, 5, 1, 2),  // red
        46: const Rect.fromLTWH(0, 7, 1, 2),  // black
        47: const Rect.fromLTWH(0, 9, 1, 2),  // odd
        48: const Rect.fromLTWH(0, 11, 1, 2), // 19-36
      };
    } else {
      inputSize = const Size(rows, cols);
      rects = {
        0: const Rect.fromLTWH(0, 0, 1, 6), // 0
        for (int i = 0; i < 36; i++)
          i + 1: Rect.fromLTWH(1 + (i ~/ 3).toDouble(), 4 - 2 * (i % 3), 1, 2),
        37: const Rect.fromLTWH(13, 4, 1, 2), // 2 to 1 #1
        38: const Rect.fromLTWH(13, 2, 1, 2), // 2 to 1 #2
        39: const Rect.fromLTWH(13, 0, 1, 2), // 2 to 1 #3
        40: const Rect.fromLTWH(1, 6, 4, 1),  // 1st 12
        41: const Rect.fromLTWH(5, 6, 4, 1),  // 2nd 12
        42: const Rect.fromLTWH(9, 6, 4, 1),  // 3rd 12
        43: const Rect.fromLTWH(1, 7, 2, 1),  // 1-18
        44: const Rect.fromLTWH(3, 7, 2, 1),  // even
        45: const Rect.fromLTWH(5, 7, 2, 1),  // red
        46: const Rect.fromLTWH(7, 7, 2, 1),  // black
        47: const Rect.fromLTWH(9, 7, 2, 1),  // odd
        48: const Rect.fromLTWH(11, 7, 2, 1), // 19-36
      };
    }
    final fs = applyBoxFit(BoxFit.contain, inputSize, size);
    final scale = fs.destination.width / fs.source.width;
    final offset = Alignment.center.inscribe(fs.destination, Offset.zero & size).topLeft;
    for (int i = 0; i <= 48; i++) {
      layoutChild(i, BoxConstraints.tight(rects[i]!.size * scale));
      positionChild(i, rects[i]!.topLeft.scale(scale, scale) + offset);
    }
  }

  @override
  bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => false;
}

class RouletteNumber extends StatelessWidget {
  RouletteNumber({
    required this.text,
    required this.color,
    required this.selected,
    required this.orientation,
    required this.decoration,
    this.onTap,
  });

  final String text;
  final Color color;
  final bool selected;
  final Orientation orientation;
  final BoxDecoration decoration;
  final GestureTapCallback? onTap;

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: decoration,
      child: Center(
        child: Padding(
          padding: const EdgeInsets.all(2.5),
          child: AspectRatio(
            aspectRatio: orientation == Orientation.portrait? 4 / 3 : 3 / 4,
            child: RotatedBox(
              quarterTurns: orientation == Orientation.landscape? 3 : 0,
              child: Material(
                clipBehavior: Clip.antiAlias,
                color: color,
                shape: selected? RoundedRectangleBorder() : StadiumBorder(),
                child: InkWell(
                  onTap: onTap,
                  child: FittedBox(
                    child: Text(text, style: TextStyle(color: selected? Colors.orange : Colors.white54)),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Upvotes: 1

Related Questions