CustomMultiChildLayout - wurzelsand/flutter-memos GitHub Wiki

CustomMultiChildLayout, Flow, LayoutBuilder, Flex

CustomMultiChildLayout

CustomMultiChildLayout soll aus zwei Children bestehen, aus einem roten und einem grünen Rechteck. Das Delegate soll sich um das Layout kümmern: Die beiden Rechtecke sollen übereinander stehen, wenn der vorhandene Platz für die Rechtecke höher als breit ist. Ansonsten sollen sie nebeneinander stehen.

Ausführung

import 'package:flutter/material.dart';

void main() => runApp(const ExampleApp());

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

  @override
  Widget build(BuildContext context) => const MaterialApp(
        home: Scaffold(
          body: ExampleWidget(),
        ),
      );
}

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

  @override
  Widget build(BuildContext context) => CustomMultiChildLayout(
        delegate: _TwoChildrenLayoutDelegate(ids: [1, 2]),
        children: [
          LayoutId(
            id: 1,
            child: Container(color: Colors.red, ),
          ),
          LayoutId(
            id: 2,
            child: Container(color: Colors.green, ),
          ),
        ],
      );
}

class _TwoChildrenLayoutDelegate extends MultiChildLayoutDelegate {
  _TwoChildrenLayoutDelegate({required this.ids}) {
    assert(ids.length == 2);
  }

  final List<int> ids;

  @override
  void performLayout(Size size) {
    final useVerticalLayout = size.height > size.width;
    for (final id in ids) {
      final childSize = useVerticalLayout
          ? layoutChild(id,
              BoxConstraints(maxWidth: size.width, maxHeight: size.height / 2))
          : layoutChild(id,
              BoxConstraints(maxWidth: size.width / 2, maxHeight: size.height));
      Offset childPosition = Offset.zero;
      for (final id in ids) {
        positionChild(id, childPosition);
        if (useVerticalLayout) {
          childPosition += Offset(0, childSize.height);
        } else {
          childPosition += Offset(childSize.width, 0);
        }
      }
    }
  }

  @override
  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => false; // #1
}

Anmerkungen

  1. Damit ein performLayout auch dann ausgeführt wird, wenn sich das Delegate ändert. Hier würde man die Felder des neuen und alten Delegates vergleichen. Wenn sich dabei ein Unterschied ergibt, könnte ich hier true zurück geben.
  • Die entscheidenen Methoden des MultiChildLayoutDelegate sind layoutChild (für die Größe) und positionChild (für die Position).

Das gleiche mit FlowDelegate

Ich probiere, ob das gleiche auch mit Flow geht. Dabei berechne ich die Größe der Children bereits im ExampleWidget und das Delegate kümmert sich nur um die Positionierung. Ich glaube, dass der Einsatzbereich von Flow eher in Animationen liegt.

Ausführung

import 'package:flutter/material.dart';

void main() => runApp(const ExampleApp());

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

  @override
  Widget build(BuildContext context) => const MaterialApp(
    home: Scaffold(
      body: ExampleWidget(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    final screenSize = MediaQuery.of(context).size;
    final childSize = screenSize.height > screenSize.width
        ? Size(screenSize.width, screenSize.height / 2)
        : Size(screenSize.width / 2, screenSize.height);
    return Flow(
        delegate: const _TwoChildrenLayoutDelegate(),
        children: [
          Container(
            color: Colors.red,
            width: childSize.width,
            height: childSize.height
          ),
          Container(
            color: Colors.green,
            width: childSize.width,
            height: childSize.height,
          ),
        ],
    );
  }
}

class _TwoChildrenLayoutDelegate extends FlowDelegate {
  const _TwoChildrenLayoutDelegate();

  @override
  void paintChildren(FlowPaintingContext context) {
    double dx = 0, dy = 0;

    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i, transform: Matrix4.translationValues(dx, dy, 0));
      if (context.size.height > context.size.width) {
        dy = context.getChildSize(i)!.height;
      } else {
        dx = context.getChildSize(i)!.width;
      }
    }
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) => false;
}

Das gleiche mit LayoutBuilder und Flex

Natürlich braucht man kein Delegate für die Lösung der Aufgabe. z. B.:

import 'package:flutter/material.dart';

void main() => runApp(const ExampleApp());

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

  @override
  Widget build(BuildContext context) => const MaterialApp(
    home: Scaffold(
      body: ExampleWidget(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final useVerticalLayout = constraints.maxHeight > constraints.maxWidth;
        return Flex(
          direction: useVerticalLayout ? Axis.vertical : Axis.horizontal,
          children: [
            Expanded(child: Container(color: Colors.red)),
            Expanded(child: Container(color: Colors.green)),
          ],
        );
      },
    );
  }
}
⚠️ **GitHub.com Fallback** ⚠️