onLayout() ist eine Methode der ViewGroup-Klasse, die die Positionen und Größen von Child-Views auf der Koordinatenebene des Elterncontainers bestimmt. Das Android-System ruft onLayout nach der Messphase (onMeasure) auf, wenn für jede Child-View die gemessene Breite und Höhe bereits bekannt sind. Laut Android Developers Documentation (2026) ist onLayout eine obligatorische Methode zum Überschreiben in jeder benutzerdefinierten ViewGroup, da die Standardimplementierung von ViewGroup keine automatische Positionierung der Kinder durchführt.
Wichtige Punkte
onLayout(boolean changed, int l, int t, int r, int b) ist eine protected-Methode der ViewGroup-Klasse, die vom System aufgerufen wird, um Child-Views innerhalb des Elterncontainers zu positionieren. Der Entwickler überschreibt diese Methode, wenn er eine benutzerdefinierte ViewGroup mit nicht standardmäßiger Elementanordnung erstellt: kaskadierend, gitterförmig, versetzt oder nach beliebigen Koordinaten. Jede Child-View erhält ihre endgültigen Grenzen durch einen Aufruf von child.layout().
Der Parameter changed gibt an, ob sich die Position oder Größe der ViewGroup selbst seit dem letzten Layout geändert hat. Wenn changed true ist, müssen wahrscheinlich alle Kindelemente neu positioniert werden. Die Parameter l, t, r, b sind die Koordinaten der oberen linken und unteren rechten Ecke der ViewGroup im Koordinatensystem ihres Eltern-Elements. Innerhalb von onLayout verwendet der Entwickler diese Werte als Startkoordinaten für die Anordnung der Kinder.
ViewGroup ist die einzige Klasse, die onLayout überschreibt. Eine normale View (keine ViewGroup) hat keine Kindelemente und benötigt kein onLayout — ihre Positionierung wird vom Elterncontainer übernommen. Selbst wenn eine normale View onLayout überschreibt, wird das System es nicht aufrufen. Dies ist ein grundlegender Unterschied zu onMeasure, das für jede View aufgerufen wird.
Die Layout-Phase beginnt mit einem Aufruf der öffentlichen Methode layout(int l, int t, int r, int b) auf der Root-View. Diese Methode setzt die endgültigen Koordinaten der View selbst und ruft onLayout auf, wenn die View eine ViewGroup ist. Dann ruft onLayout rekursiv child.layout() für jedes Kindelement auf, und der Prozess wiederholt sich die Hierarchie hinunter. Auf diese Weise propagiert sich das Layout von der Wurzel zu den Blättern.
Vor dem Aufruf von onLayout prüft das System, ob sich die Abmessungen der View im Vergleich zum vorherigen Zyklus geändert haben. Wenn sich die Abmessungen nicht geändert haben und requestLayout nicht aufgerufen wurde, wird onLayout möglicherweise nicht aufgerufen — das System verwendet die Ergebnisse des vorherigen Layouts. Dies ist eine Optimierung, die unnötige Positionsneuberechnungen während Animationen oder Scrollen verhindert, wenn sich nur der Inhalt ändert, nicht aber die Abmessungen.
requestLayout() ist eine View-Methode, die dem System mitteilt, dass das Layout der View veraltet ist und neu berechnet werden muss. Der Aufruf von requestLayout löst einen vollständigen Zyklus aus: Zuerst wird onMeasure aufgerufen, dann onLayout, dann onDraw. Im Gegensatz zu invalidate, das nur das Neuzeichnen auslöst, löst requestLayout eine vollständige Neuberechnung von Abmessungen und Positionen aus. Übermäßige Aufrufe von requestLayout sind eine häufige Ursache für Leistungsprobleme.
l (left) — die X-Koordinate des linken Rands der ViewGroup im Koordinatensystem ihres Eltern-Elements. t (top) — die Y-Koordinate des oberen Rands. r (right) — die X-Koordinate des rechten Rands. b (bottom) — die Y-Koordinate des unteren Rands. Die Breite der ViewGroup wird als r - l berechnet, die Höhe als b - t. Diese Koordinaten enthalten bereits das gesamte Padding der ViewGroup selbst.
Innerhalb von onLayout ruft der Entwickler child.layout(int childLeft, int childTop, int childRight, int childBottom) für jede Child-View auf. Die an child.layout übergebenen Koordinaten müssen im Koordinatensystem der Eltern-ViewGroup liegen. Normalerweise werden childLeft und childTop unter Berücksichtigung des Padding des Eltern-Elements berechnet: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Parameter | Beschreibung | Typische Verwendung |
|---|---|---|
| l (left) | Koordinate des linken Rands der ViewGroup im Eltern-Element | Startpunkt auf der X-Achse für Kindelemente |
| t (top) | Koordinate des oberen Rands der ViewGroup im Eltern-Element | Startpunkt auf der Y-Achse für Kindelemente |
| r (right) | Koordinate des rechten Rands der ViewGroup im Eltern-Element | Obere Breitengrenze, r - l = getWidth() |
| b (bottom) | Koordinate des unteren Rands der ViewGroup im Eltern-Element | Obere Höhengrenze, b - t = getHeight() |
Kindkoordinaten werden nach der Formel berechnet: childLeft = l + paddingLeft + (marginLeft falls vorhanden), childRight = childLeft + child.getMeasuredWidth(). Analog für die vertikale Achse: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Nach Berechnung dieser vier Werte wird child.layout(childLeft, childTop, childRight, childBottom) aufgerufen.
Erstellen wir ein FlowLayout — eine benutzerdefinierte ViewGroup, die Child-Views in Zeilen anordnet und Elemente in eine neue Zeile umbricht, wenn die aktuelle Zeile voll ist. Dies ist ein Pendant zu Flexbox mit Wrap in einer Ebene. onLayout durchläuft alle Child-Views, berechnet die Position für jede und ruft child.layout() mit korrekten Grenzen auf.
class FlowLayout(context: Context)
: ViewGroup(context) {
private val horizontalSpacing = 12
private val verticalSpacing = 8
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val parentWidth =
MeasureSpec.getSize(widthMeasureSpec)
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec, 0,
heightMeasureSpec, 0)
if (rowX + child.measuredWidth >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
rowX += child.measuredWidth +
horizontalSpacing
maxRowHeight = maxOf(maxRowHeight,
child.measuredHeight)
}
val totalHeight = rowY + maxRowHeight +
paddingBottom
setMeasuredDimension(
resolveSize(parentWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
val parentWidth = r - l
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
val cw = child.measuredWidth
val ch = child.measuredHeight
if (rowX + cw >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
child.layout(rowX, rowY,
rowX + cw, rowY + ch)
rowX += cw + horizontalSpacing
maxRowHeight =
maxOf(maxRowHeight, ch)
}
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams =
MarginLayoutParams(context, attrs)
}
onMeasure und onLayout sind zwei aufeinanderfolgende Phasen des View-Lebenszyklus, die grundlegend unterschiedliche Aufgaben ausführen. onMeasure bestimmt die gewünschten (gemessenen) Abmessungen einer View, während onLayout die tatsächlichen (endgültigen) Koordinaten und Abmessungen festlegt. Der Hauptunterschied: In onMeasure können Abmessungen vorläufig sein und später vom Eltern-Element angepasst werden, während in onLayout die endgültige Position jeder Child-View fixiert wird.
onMeasure wird für jede View aufgerufen, einschließlich Blatt-Views (TextView, ImageView, Button). onLayout wird nur für ViewGroup aufgerufen. Dies liegt daran, dass die Positionierung in der Verantwortung des Elterncontainers liegt, nicht der View selbst. Eine Blatt-View erhält ihre Position durch layout(), das vom onLayout des Eltern-Elements aufgerufen wird.
getMeasuredWidth() und getMeasuredHeight() sind nach onMeasure verfügbar, während getWidth() und getHeight() erst nach onLayout verfügbar sind. Wenn Sie innerhalb von onMeasure auf getWidth() zugreifen, wird der Wert des vorherigen Zyklus oder null zurückgegeben. Daher sollten Sie zur Berechnung von Abmessungen in onMeasure MeasureSpec und die Kinder sequenziell verwenden.
Positionierung ohne Berücksichtigung von Padding — der erste Fehler bei der Implementierung von onLayout. Der Entwickler vergisst oft, das paddingLeft und paddingTop des Eltern-Elements zu den Startkoordinaten der Child-Views hinzuzufügen. Infolgedessen werden die Kinder am Rand der ViewGroup angezeigt und ignorieren das über setPadding() oder in XML-Markup gesetzte Padding. Korrekte Berechnung: childLeft = paddingLeft + offsetX.
Aufruf von Layout für unsichtbare Kinder — das zweite häufige Problem. Wenn eine ViewGroup Child-Views mit Sichtbarkeit GONE enthält, müssen sie nicht positioniert werden — sie nehmen keinen Platz ein. Allerdings muss onLayout diesen Fall korrekt behandeln, indem es GONE-Kinder überspringt. Für INVISIBLE-Kinder muss Layout weiterhin aufgerufen werden — sie behalten ihren Platz, auch wenn sie nicht angezeigt werden.
Ignorieren des changed-Parameters — der dritte Fehler. Der changed-Parameter gibt an, ob sich die Abmessungen oder die Position der ViewGroup geändert haben. Wenn changed == false ist, können zwischengespeicherte Koordinaten verwendet werden, ohne das Layout aller Kindelemente neu zu berechnen. Allerdings ist die vollständige Layout-Zwischenspeicherung eine komplexe Aufgabe, und in den meisten Implementierungen berechnet onLayout einfach jedes Mal alle Elemente neu. Dies ist bei einer kleinen Anzahl von Kindern akzeptabel.
Häufig gestellte Fragen
Ja, das ist möglich, wenn die ViewGroup Standard-LayoutParams verwendet und keine benutzerdefinierte Positionierungslogik hinzufügt. Allerdings führt die Standardimplementierung von onLayout in ViewGroup keine Aktionen aus — die Kindelemente werden nicht positioniert. In der Praxis überschreiben alle ViewGroup (LinearLayout, RelativeLayout, FrameLayout) onLayout.
layout() ist eine öffentliche finale Methode von View, die vom System oder der Eltern-ViewGroup aufgerufen wird. Sie setzt die Koordinaten der View selbst und ruft onLayout auf, wenn die View eine ViewGroup ist. onLayout() ist eine protected-Methode, die der Entwickler für die benutzerdefinierte Anordnung von Kindelementen überschreibt.
Technisch — ja, kann es. Dies wird jedoch dringend nicht empfohlen, da es zu einer Endlosschleife führt: requestLayout → onMeasure → onLayout → requestLayout. Wenn requestLayout innerhalb von onLayout aufgerufen wird, löst das System eine StackOverflowError-Ausnahme aus. Alle Dimensionsänderungen sollten vor onLayout durchgeführt werden.
Layout-Animationen (LayoutTransition) fangen Änderungen der Positionen von Child-Views ab und wenden Übergangsanimationen an. Wenn LayoutTransition aktiviert ist, setzt onLayout zunächst die endgültigen Positionen, dann animiert LayoutTransition die Bewegung von der alten zur neuen Position. Dies erfordert eine korrekte onLayout-Implementierung mit geeigneten Endkoordinaten.
invalidate() löst nur die Draw-Phase (Neuzeichnen) aus, ohne Measure und Layout zu beeinflussen. Um onLayout auszulösen, müssen Sie requestLayout() aufrufen, das den vollständigen Zyklus startet: measure → layout → draw. invalidate ist effizienter zum Aktualisieren des Erscheinungsbilds, wenn sich Abmessungen und Positionen nicht ändern.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch