Fortgeschrittenes Flutter

In diesem Abschnitt werden einige fortgeschrittene Flutter-Konzepte vorgestellt, die Ihnen helfen sollen, Ihre Entwicklungsfähigkeiten zu verbessern.


Rendering-Prinzipien

Flutter verwendet die Skia-Rendering-Engine (seit 3.0 wird schrittweise auf Impeller umgestellt), um die UI direkt zu zeichnen, ohne native Komponenten zu verwenden.

Widget-Baum und Element-Baum

  • Widget-Baum: Konfigurationsinformationen, die die UI beschreiben (unveränderlich)
  • Element-Baum: Instanziiertes Objekt von Widget (veränderlich)
  • RenderObject-Baum: Verantwortlich für das tatsächliche Rendering

Das Verständnis der Rendering-Prinzipien hilft, die UI-Performance zu optimieren und unnötige Neuaufbauten zu vermeiden.


InheritedWidget - Datenweitergabe

InheritedWidget ermöglicht es, Daten im Widget-Baum nach unten weiterzugeben, und untergeordnete Widgets können auf Daten von Vorfahren zugreifen.

Beispiel: Benutzerdefiniertes InheritedWidget

// Datencontainer definieren
class MyData extends InheritedWidget {
  final int value;

  const MyData({
    super.key,
    required this.value,
    required super.child,
  });

  // Komfortmethode: das nächstgelegene MyData abrufen
  static MyData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyData>()!;
  }

  // Bestimmen, ob das untergeordnete Widget neu aufgebaut werden muss
  @override
  bool updateShouldNotify(MyData oldWidget) {
    return value != oldWidget.value;
  }
}

// Verwendung
class ParentWidget extends StatelessWidget {
  const ParentWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return MyData(
      value: 42,
      child: const ChildWidget(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    // MyData des Vorfahren abrufen
    final myData = MyData.of(context);
    return Text('Wert: ${myData.value}');
  }
}

RepaintBoundary - Lokale Neuzeichnung

Die Verwendung von RepaintBoundary kann den Neuzeichnungsbereich einschränken und die Leistung verbessern.

Beispiel: Verwendung von RepaintBoundary

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

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: Container(
        // Das Zeichnen in diesem Bereich wirkt sich nicht auf das Äußere aus
        color: Colors.red,
        child: const Text(Unabhängiger Bereich),
      ),
    );
  }
}

Tiefes Verständnis von Keys

Keys helfen Flutter, Widgets zu identifizieren und neue Widgets von vorhandenen Widgets zu unterscheiden.

ValueKey vs ObjectKey vs UniqueKey

TypeBeschreibung
ValueKeyWerte als Keys verwenden (wie IDs, Zahlen, Strings)
ObjectKeyObjektverweise als Keys verwenden
UniqueKeyBei jeder Erstellung, Zerstörung und Neuerstellung einen eindeutigen Key erzeugen

CustomPainter - Benutzerdefiniertes Zeichnen

Mit CustomPainter kann vollständig benutzerdefiniertes Grafikzeichnen erreicht werden.

Beispiel: Verwendung von CustomPainter

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

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: MyCirclePainter(),
      size: const Size(200, 200),
    );
  }
}

class MyCirclePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Einen Kreis zeichnen
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;

    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2 - 10;

    canvas.drawCircle(center, radius, paint);

    // Einen Rahmen zeichnen
    final borderPaint = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2;

    canvas.drawCircle(center, radius, borderPaint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

Stream und Future

Beispiel: Stream-Verwendung

// Einen Stream erstellen
Stream<int> countStream(int max) async* {
  for (int i = 1; i <= max; i++) {
    await Future.delayed(const Duration(seconds: 1));
    yield i;  // Wert senden
  }
}

// StreamBuilder verwenden
class StreamExample extends StatelessWidget {
  const StreamExample({super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: countStream(10),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text(Anzahl: ${snapshot.data});
        } else if (snapshot.hasError) {
          return Text(Error: ${snapshot.error});
        }
        return const CircularProgressIndicator();
      },
    );
  }
}

Zusammenfassung

Dieses einführende Flutter-Tutorial behandelt die folgenden Themen:

  • Flutter-Installation und Umgebungskonfiguration
  • Widget-Grundlagen (StatelessWidget und StatefulWidget)
  • Layout-System (Row, Column, Stack)
  • Verarbeitung von Benutzereingaben
  • State-Management (setState, Provider)
  • Netzwerkanfragen und Datenspeicherung
  • Navigation und Routing
  • Testen und Veröffentlichung

Für weiterführendes Lernen folgen Sie:

  • BLoC-Muster
  • Riverpod-Zustandsverwaltung
  • GetX-Routing und Zustandsverwaltung
  • Flutter-Leistungsoptimierung
  • Flutter-Web- und Desktop-Entwicklung
Weitere Erweiterungen