Beseitige unnötige Rebuilds dank const, Keys und Reconciliation und miss dann mit DevTools.
Diese Lektion in Kodokon öffnenFlutter arbeitet mit drei Bäumen: den Widgets (unveränderliche Wegwerf-Konfigurationen), den Elements (lebenden Instanzen, die den Zustand tragen) und den Render Objects (Layout, Zeichnen). Ein Widget neu zu bauen ist billig; teuer sind das Layout und das kaskadierende Zeichnen. Bei einem Rebuild wendet Element.updateChild drei Regeln der Reihe nach an: Ist das neue Widget identisch (dieselbe Instanz) mit dem alten, wird der gesamte Teilbaum übersprungen; ist Widget.canUpdate wahr (gleicher runtimeType, gleicher key), bleibt das Element erhalten und wird einfach neu konfiguriert; andernfalls wird das Element zerstört und neu aufgebaut, inklusive Zustand. Das Schlüsselwort const nutzt die erste Regel aus: Ein konstanter Ausdruck wird vom Compiler kanonisiert, bei jedem Build kommt dieselbe Instanz zurück, identical ist wahr - und der ganze Zweig wird übersprungen.
import 'package:flutter/material.dart';
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() =>
_CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
const _Header(),
Text('Total: $_count'),
FilledButton(
onPressed: () => setState(() => _count++),
child: const Text('Increment'),
),
],
);
}
}
class _Header extends StatelessWidget {
const _Header();
@override
Widget build(BuildContext context) {
debugPrint('build _Header');
return const Text('Counter');
}
}Bei Listen von Kindern paart die Reconciliation zuerst nach Position. Ordne zustandsbehaftete Einträge ohne Keys um, und jedes Element behält den Zustand des vorherigen Bewohners seiner Position: Häkchen an der falschen Stelle, vertauschte Textfelder. Ein Key ändert die Regel für die Paarung: Der Algorithmus findet das Element mit demselben Key, auch wenn es innerhalb der Liste verschoben wurde. Verwende ValueKey mit einem stabilen fachlichen Bezeichner. GlobalKey geht weiter - damit kannst du einen ganzen Teilbaum umhängen, ohne seinen Zustand zu verlieren -, aber er hat echte Kosten (globales Register, Eindeutigkeit im gesamten Baum): Hebe ihn dir für echte Bedürfnisse auf, etwa Form oder das Umhängen von Teilbäumen.
import 'package:flutter/material.dart';
class TodoList extends StatelessWidget {
const TodoList({super.key, required this.items});
final List<String> items;
@override
Widget build(BuildContext context) {
return ListView(
children: [
for (final item in items)
Dismissible(
key: ValueKey(item),
onDismissed: (direction) {},
child: ListTile(title: Text(item)),
),
],
);
}
}Miss, bevor du optimierst. In den DevTools zeichnet die Ansicht Performance die Zeit pro Frame für die beiden kritischen Threads auf: UI (dein Dart-Code: Build, Layout) und Raster (das Rastern der Layer). Das Budget liegt bei etwa 16 ms bei 60 Hz. Ruckler auf der Raster-Seite bekommst du nicht in den Griff, indem du deine Builds optimierst: Sie kommen von den Kosten des Zeichnens (Schatten, saveLayer, überdimensionierte Bilder). Aktiviere Track widget builds, um zu sehen, wer neu baut, und debugRepaintRainbowEnabled, um die neu gezeichneten Zonen sichtbar zu machen - der Rahmen wechselt bei jedem Repaint die Farbe. RepaintBoundary isoliert einen Teilbaum in seinem eigenen Layer: Ein Spinner, der ununterbrochen neu zeichnet, macht seine Nachbarn nicht mehr schmutzig.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
debugRepaintRainbowEnabled = true;
runApp(const DemoApp());
}
class DemoApp extends StatelessWidget {
const DemoApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
showPerformanceOverlay: true,
home: Scaffold(
body: Center(
child: RepaintBoundary(
child: CircularProgressIndicator(),
),
),
),
);
}
}