Fix: BoxConstraints forces an infinite height in Flutter
Ein Widget hat height: double.infinity angefordert, obwohl der Parent keine Höhenbegrenzung hat, etwa eine Column oder ListView. Verwenden Sie stattdessen Expanded, eine endliche Höhe, LimitedBox oder SliverFillRemaining.
BoxConstraints forces an infinite height bedeutet, dass ein Widget exakt double.infinity Pixel Höhe angefordert hat und sein Parent keine Höhenbegrenzung hatte, auf die sich diese Anforderung zurechtstutzen ließe. Die üblichen Verdächtigen sind SizedBox(height: double.infinity), Container(height: double.infinity), SizedBox.expand oder BoxConstraints.expand(), direkt in einer Column, einer ListView oder einer SingleChildScrollView platziert. Die Lösung: Fordern Sie nichts “Unendliches” an, wo nichts endlich ist. Verwenden Sie Expanded innerhalb einer Column, geben Sie der Box eine konkrete Zahl, packen Sie sie in eine LimitedBox, oder wechseln Sie zu SliverFillRemaining, wenn der Rest des Bildschirms gefüllt werden soll, der Inhalt aber trotzdem scrollen muss. Alles Folgende wurde mit Flutter 3.44.8 (stable) und Dart 3.12.2 reproduziert.
Verwirrend ist, dass height: double.infinity eine völlig normale Schreibweise ist. Meistens funktioniert sie. Sie scheitert nur dann, wenn der nächste Vorfahre, der die Höhenbeschränkung setzt, sagt “wähle eine beliebige Höhe”, und genau das sagen Columns und Scroll-Views.
Der Fehler im Kontext
Dies ist der erste Block, den Flutter ausgibt. Den Stack Trace habe ich gekürzt, er umfasst etwa 100 Frames von RenderProxyBoxMixin.performLayout:
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═══════════════════════════════
The following assertion was thrown during performLayout():
BoxConstraints forces an infinite height.
These invalid constraints were provided to _RenderColoredBox's layout() function by the following
function, which probably computed the invalid constraints in question:
RenderConstrainedBox.performLayout (package:flutter/src/rendering/proxy_box.dart:296:14)
The offending constraints were:
BoxConstraints(0.0<=w<=800.0, h=Infinity)
The relevant error-causing widget was:
SizedBox
Drei Zeilen enthalten alle nötigen Informationen:
h=Infinityin den beanstandeten Constraints. Das ist eine feste unendliche Höhe: Minimum und Maximum sind beide unendlich. Das kann nichts erfüllen.RenderConstrainedBox.performLayoutist die Funktion, die sie berechnet hat.RenderConstrainedBoxist das Render-Objekt hinterSizedBox,ConstrainedBoxund dem Größenanteil vonContainer. Der Übeltäter ist also fast immer eines dieser drei.- “The relevant error-causing widget was” nennt Datei und Zeile dieses Widgets. Klicken Sie in Ihrer IDE darauf.
Unterhalb dieses Blocks folgt eine Kaskade von RenderBox was not laid out-Assertions, eine pro Vorfahre, dazu eine für Scaffold. Das sind Folgefehler. Beheben Sie den ersten Fehler, und alle verschwinden. Wenn Sie von dieser Kaskade hierhergekommen sind: Die Erläuterung zu RenderBox was not laid out erklärt, warum sie sich so aufstaut.
Die Schwestermeldung BoxConstraints forces an infinite width. ist derselbe Fehler um 90 Grad gedreht, und BoxConstraints forces an infinite width and infinite height. sind beide zugleich. Alles in diesem Beitrag gilt für sie mit vertauschter Breite und Höhe.
Warum das passiert
Das Flutter-Layout folgt einer Regel: Constraints gehen nach unten, Größen gehen nach oben, der Parent legt die Position fest. Jeder Parent übergibt seinem Kind ein BoxConstraints mit Minimum und Maximum für Breite und Höhe.
Wenn Sie SizedBox(height: double.infinity) schreiben, setzen Sie die Höhe nicht auf unendlich. Sie fordern feste Constraints mit minHeight: infinity, maxHeight: infinity an, und RenderConstrainedBox gleicht diese Anforderung dann mit dem ab, was sein eigener Parent erlaubt hat, über BoxConstraints.enforce:
// Flutter 3.44.8, package:flutter/src/rendering/box.dart (simplified)
BoxConstraints enforce(BoxConstraints constraints) {
return BoxConstraints(
minHeight: clampDouble(minHeight, constraints.minHeight, constraints.maxHeight),
maxHeight: clampDouble(maxHeight, constraints.minHeight, constraints.maxHeight),
// width is clamped the same way
);
}
Dieses Begrenzen ist der Grund, warum die Schreibweise normalerweise funktioniert. Im Body eines Scaffold sagt der Parent 0 <= h <= 600, also wird unendlich auf 600 begrenzt und die Box füllt den Bildschirm. Eine Column gibt dagegen jedem Kind ohne Flex auf der Hauptachse 0 <= h <= Infinity, und eine vertikale ListView oder SingleChildScrollView tut dasselbe. Unendlich auf ein Maximum von unendlich zu begrenzen ergibt unendlich. Die resultierenden Constraints werden an layout() des Kindes übergeben, das debugAssertIsValid(isAppliedConstraint: true) ausführt, ein unendliches Minimum feststellt und eine Exception wirft.
Die Regel zum Merken: double.infinity bedeutet “so groß, wie mein Parent es erlaubt”. Das ist nur sicher, wenn der Parent etwas Endliches erlaubt.
Ein minimaler Reproduktionsfall zum Einfügen in eine neue App
Jeder dieser drei Bodies wirft den Fehler unter Flutter 3.44.8. Ich habe sie als Widget-Tests innerhalb von MaterialApp(home: Scaffold(body: ...)) ausgeführt:
// Flutter 3.44.8, Dart 3.12.2
import 'package:flutter/material.dart';
// 1. A Column gives children unbounded height.
const columnRepro = Column(
children: [
Text('Header'),
SizedBox(
height: double.infinity,
child: ColoredBox(color: Colors.red),
),
],
);
// 2. A vertical ListView gives children unbounded height.
final listRepro = ListView(
children: [
Container(height: double.infinity, color: Colors.red),
],
);
// 3. SizedBox.expand and BoxConstraints.expand() are the same request in disguise.
const scrollRepro = SingleChildScrollView(
child: Column(
children: [
SizedBox.expand(child: ColoredBox(color: Colors.red)),
],
),
);
Die beanstandeten Constraints unterscheiden sich leicht: Der Column-Fall meldet BoxConstraints(0.0<=w<=800.0, h=Infinity), weil die Querachse einer Column lose ist, während die Fälle ListView und SizedBox.expand BoxConstraints(w=800.0, h=Infinity) melden. Derselbe Fehler, dieselbe Lösung.
Ein irreführendes Detail: Hat die SizedBox kein Kind, erscheint diese Meldung gar nicht. Stattdessen erhalten Sie RenderConstrainedBox object was given an infinite size during layout, weil es kein Kind gibt, auf dem layout() aufgerufen werden könnte, und die Box versucht, sich selbst auf unendlich zu dimensionieren. Gleiche Ursache, andere Formulierung.
Lösungen im Detail
Wählen Sie die Lösung danach, was die unendliche Höhe eigentlich bewirken sollte.
1. “Den restlichen Platz in der Column füllen”: Expanded verwenden
Das ist die häufigste Absicht. Innerhalb einer Column sagt man “nimm, was übrig ist” mit einem Flex-Kind, nicht mit einer unendlichen Größe:
// Flutter 3.44.8, Dart 3.12.2
Column(
children: [
const Text('Header'),
Expanded(
child: Container(color: Colors.red), // no height at all
),
],
)
In meinem Test mit einer 800x600-Fläche ergab die rote Box Size(800.0, 580.0): die volle Höhe abzüglich der Kopfzeile. Expanded funktioniert, weil die Column Flex-Kinder zuletzt layoutet, nachdem sie weiß, wie viel Platz die Kinder fester Größe belegt haben, und ihnen eine feste, endliche Höhe übergibt.
Das funktioniert nur, wenn die Column selbst eine begrenzte Höhe hat. Steckt diese Column in einer SingleChildScrollView, tauscht Expanded diesen Fehler gegen RenderFlex children have non-zero flex but incoming height constraints are unbounded. Das ist dasselbe Problem eine Ebene höher, und die Lösungen 3 und 4 behandeln es.
2. “Ich will es einfach hoch”: eine endliche Zahl vorgeben
Liegt die Box in einer Scroll-View, wird sie scrollen, “den Bildschirm füllen” war also meist nicht gemeint. Geben Sie ihr eine konkrete Höhe oder leiten Sie eine aus dem Bildschirm ab:
// Flutter 3.44.8, Dart 3.12.2
Builder(
builder: (context) => ListView(
children: [
SizedBox(
height: MediaQuery.sizeOf(context).height * 0.5,
child: const ColoredBox(color: Colors.red),
),
// ... more children
],
),
)
Das ergab eine Box von Size(800.0, 300.0) auf einer 600 Pixel hohen Fläche. Bevorzugen Sie MediaQuery.sizeOf(context) gegenüber MediaQuery.of(context).size: Es löst nur dann einen Rebuild aus, wenn sich die Größe ändert, nicht bei jeder MediaQuery-Änderung wie etwa Tastatur-Insets.
Ist das Widget wiederverwendbar und Sie wissen nicht, ob es in einem begrenzten oder unbegrenzten Parent landet, verwenden Sie LimitedBox. Sie tut nichts, wenn der Parent begrenzt ist, und deckelt das Maximum, wenn er es nicht ist:
// Flutter 3.44.8, Dart 3.12.2
ListView(
children: [
LimitedBox(
maxHeight: 200,
child: Container(height: double.infinity, color: Colors.red),
),
],
)
Innerhalb der ListView wurde dieser Container ohne Fehler mit Size(800.0, 200.0) gelayoutet. Setzen Sie dasselbe Widget in einen begrenzten Parent, füllt es stattdessen den Parent. Dieses Muster empfiehlt der offizielle Leitfaden “Understanding constraints” für genau diese Situation.
3. “Den Bildschirm füllen, aber scrollen, wenn der Inhalt höher ist”: SliverFillRemaining
Das ist der Fall des Login-Formulars: Eine Column soll bis zum unteren Rand des Viewports reichen, damit ein Button unten sitzen kann, aber auf kleinen Smartphones oder bei geöffneter Tastatur scrollen. SliverFillRemaining mit hasScrollBody: false ist das dafür gebaute Widget:
// Flutter 3.44.8, Dart 3.12.2
CustomScrollView(
slivers: [
const SliverToBoxAdapter(child: SizedBox(height: 100)),
SliverFillRemaining(
hasScrollBody: false,
child: Container(color: Colors.red),
),
],
)
Die rote Box erhielt Size(800.0, 500.0): genau den Viewport abzüglich der 100 Pixel hohen Kopfzeile. hasScrollBody: false teilt dem Sliver mit, dass sein Kind selbst nicht scrollbar ist, sodass er das Kind mindestens auf die verbleibende Ausdehnung dimensioniert, bei größerer Eigenhöhe des Kindes auf diese. Ist das Kind eine ListView oder eine andere Scroll-View, lassen Sie hasScrollBody auf dem Standardwert true.
4. Dasselbe ohne Slivers: LayoutBuilder plus ConstrainedBox
Wenn Sie einen Bildschirm noch nicht auf CustomScrollView umstellen möchten, beschreibt die Dokumentation zu SingleChildScrollView ein Muster, das die Viewport-Höhe einmal ausliest und in ein Minimum statt einer festen unendlichen Größe umwandelt:
// Flutter 3.44.8, Dart 3.12.2
LayoutBuilder(
builder: (context, viewport) => SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: viewport.maxHeight),
child: IntrinsicHeight(
child: Column(
children: [
const Text('top'),
Expanded(child: Container(color: Colors.red)),
const Text('bottom'),
],
),
),
),
),
)
Der LayoutBuilder sitzt außerhalb der Scroll-View, daher ist viewport.maxHeight endlich (hier 600). ConstrainedBox gibt der Column eine Untergrenze von 600, aber keine Obergrenze, und IntrinsicHeight gibt der Column eine begrenzte Höhe, sodass Expanded zulässig ist. Mein Test ergab eine 560 Pixel hohe rote Box zwischen den beiden Textzeilen. IntrinsicHeight kostet einen zusätzlichen Layout-Durchlauf über seinen Teilbaum, was für ein Formular in Ordnung und für eine lange Liste falsch ist. Bei langen Inhalten nehmen Sie Lösung 3 oder die Optionen aus shrinkWrap vs Expanded vs slivers.
5. “Die Höhe der Row übernehmen”: IntrinsicHeight plus stretch
Eine sehr häufige Quelle dieses Fehlers in Listen ist eine farbige Seitenleiste oder ein vertikaler Trenner, der so hoch sein soll wie sein Nachbar:
// Flutter 3.44.8, Dart 3.12.2
// Throws: BoxConstraints(w=4.0, h=Infinity)
ListView(
children: const [
Row(
children: [
SizedBox(width: 4, height: double.infinity, child: ColoredBox(color: Colors.blue)),
Text('item'),
],
),
],
)
Die Row reicht ihre eigene unbegrenzte vertikale Beschränkung an die Leiste weiter. Lassen Sie die Row zuerst ihr höchstes Kind ausmessen und dann alles daran angleichen:
// Flutter 3.44.8, Dart 3.12.2
ListView(
children: const [
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(width: 4, child: ColoredBox(color: Colors.blue)),
Text('item\nline2'),
],
),
),
],
)
Die Leiste hat gar keine height: CrossAxisAlignment.stretch gibt ihr eine feste Höhe gleich der Row, und IntrinsicHeight macht diese Höhe endlich. Ein IntrinsicHeight pro Listeneintrag ist für typische Listen günstig genug.
Stolperfallen und ähnliche Fehler
double.maxFiniteist keine Lösung. Werdouble.infinitydurchdouble.maxFiniteersetzt, bringt die Assertion zum Schweigen, aber in meinem Test wurde die Box mitSize(0.0, 1.7976931348623157e+308)gelayoutet. Sie haben eine Box gebaut, die höher ist als das Universum, und alles unterhalb ist unerreichbar. Finden Sie diese “Lösung” in einem Code-Review, ist es derselbe Fehler, nur versteckt.- Die Prüfung gibt es nur im Debug-Modus.
debugAssertIsValidläuft innerhalb einesassert, Release-Builds überspringen es, und Sie erhalten statt eines roten Bildschirms eine graue Fläche oder fehlende Inhalte. Reproduzieren Sie Layout-Fehler immer im Debug-Modus. - Eine horizontale
ListViewist unproblematisch.ListView(scrollDirection: Axis.horizontal)gibt Kindern eine begrenzte Höhe (ihre eigene), also wirdheight: double.infinitydarin korrekt begrenzt: Mein Test ergabSize(100.0, 600.0). In dieser Liste ist eswidth: double.infinity, das scheitert. Vertical viewport was given unbounded heightist die umgekehrte Situation: eine Scrollable in einerColumn, statt einer unendlichen Box in einer Scrollable. Die Lösungen überschneiden sich, und der Leitfaden zu ListView in einer Column behandelt diesen Fall ausführlich.TextFieldin einerRowerzeugt unter Flutter 3.44.8 als ersten FehlerAn InputDecorator, which is typically created by a TextField, cannot have an unbounded width, nicht dieBoxConstraints-Meldung. Packen Sie das Feld inExpandedoder eineSizedBoxfester Breite.IntrinsicHeightum eineListViewwirft diesen Fehler nicht. Es wirftRenderViewport does not support returning intrinsic dimensions, weil ein Lazy-Viewport sich weigert, alle seine Kinder auszumessen. Packen Sie niemals eine Scrollable in ein Intrinsic-Widget.UnconstrainedBoxentfernt die Constraints seines Parents vollständig, daher wirft jedes unendliche Kind darin diesen Fehler selbst auf einem begrenzten Bildschirm. Setzen Sie eineLimitedBoxdazwischen oder entfernen Sie dieUnconstrainedBox.- Eine
Column, die überläuft, statt zu werfen, ist ein anderes Problem: Der Inhalt ist endlich, aber zu hoch. Siehe den Leitfaden zu RenderFlex overflowed.
Den Verursacher in einem großen Widget-Baum finden
Verweist das “relevant error-causing widget” auf eine geteilte Komponente, öffnen Sie Flutter DevTools, wählen das Widget im Widget Inspector aus und betrachten die Constraints im Layout Explorer. Gehen Sie den Baum nach oben, bis Sie den ersten Vorfahren finden, dessen Höhenbeschränkung Infinity ist: Das ist die Column, ListView, Row oder UnconstrainedBox, die die Begrenzung entfernt hat. Die Lösung gehört entweder an diesen Vorfahren (begrenzen) oder an das Kind (nicht mehr unendlich anfordern). Eine Suche im Code nach double.infinity, .expand( und BoxConstraints.expand findet den Kandidaten meist in unter einer Minute.
Verwandte Beiträge
- Fix: RenderBox was not laid out in Flutter, die Kaskade, die auf diesen Fehler folgt.
- How to nest a ListView inside a Column without an unbounded-height error.
- shrinkWrap vs Expanded vs slivers for long lists in Flutter.
- Fix: A RenderFlex overflowed in Flutter.
- Fix: RenderViewport expected a RenderSliver in a CustomScrollView, falls Sie darauf stoßen, wenn Sie zu
SliverFillRemainingwechseln.
Quellen
- Understanding constraints (Flutter-Dokumentation), einschließlich der Beispiele zu
LimitedBoxundUnconstrainedBox. - Common Flutter errors (Flutter-Dokumentation).
- BoxConstraints.enforce und BoxConstraints.debugAssertIsValid (API-Referenz).
- SingleChildScrollView, der Abschnitt “Centering, spacing, or aligning fixed-height content”.
- SliverFillRemaining und LimitedBox (API-Referenz).
packages/flutter/lib/src/rendering/box.dartundproxy_box.dartim Flutter-3.44.8-SDK, lokal gelesen.
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.