View Lifecycle — was ist das, Prozesse onMeasure onLayout onDraw

Autor: IT Sectr Veröffentlicht: 2026-03-05 Lesezeit: 10 Min.

View Lifecycle — die Abfolge von Methoden, die Android zum Zeichnen und Neuzeichnen eines Benutzeroberflächenelements (View) auf dem Bildschirm aufruft. Im Gegensatz zu Activity oder Fragment ist View eine leichte Komponente ohne erweiterten Lebenszyklus, durchläuft jedoch einen strengen Dreiphasenprozess: onMeasure (Messung), onLayout (Positionierung), onDraw (Zeichnung). Das Verständnis des View Lifecycle ist für die Erstellung benutzerdefinierter Views, die Optimierung der Leistung und die Behebung von Zeichenproblemen erforderlich. Laut Google beschleunigen benutzerdefinierte Views bei korrekter Implementierung die UI um 15–40% im Vergleich zu einer Kombination aus standardmäßig verschachtelten ViewGroups. Android-Dokumentation zu benutzerdefinierten Views beschreibt onMeasure, onLayout und onDraw als die drei Säulen des View Lifecycle.

Wichtige Punkte

  • View Lifecycle besteht aus drei Phasen: onMeasure (Größen), onLayout (Positionen), onDraw (Zeichnung) — und wird durch invalidate() oder requestLayout() ausgelöst.
  • onMeasure berechnet Breite und Höhe einer View basierend auf MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED).
  • onLayout ordnet untergeordnete Views innerhalb einer ViewGroup an und bestimmt deren left-, top-, right-, bottom-Koordinaten.
  • onDraw rendert den View-Inhalt auf Canvas: Hintergrund, Text, Formen, Bilder.
  • Falscher View Lifecycle ist die Hauptursache für UI-Leistungsprobleme (Ruckeln, verworfene Frames) und Hierarchieprobleme.

View Lifecycle — was ist das in Android

View Lifecycle ist der Prozess, den eine Android View (und ViewGroup) durchläuft, um sich auf dem Bildschirm anzuzeigen. Im Gegensatz zu Activity oder Fragment hat View kein onStart/onStop/onDestroy — sein „Leben“ besteht aus einem zyklischen Prozess des Messens, Positionierens und Zeichnens. Dieser Zyklus wird jedes Mal ausgelöst, wenn eine View angezeigt oder neu gezeichnet werden muss.

Die drei Phasen des View Lifecycle:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — bestimmt die gewünschten Abmessungen der View. Das System übergibt MeasureSpec — eine Anweisung darüber, welche Abmessungen zulässig sind (genauer Wert, Maximum oder uneingeschränkt).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — platziert die View und ihre Kinder auf dem Bildschirm. Für eine View definiert es eigene Grenzen; für eine ViewGroup positioniert es untergeordnete Elemente.
  • onDraw(Canvas canvas) — zeichnet den View-Inhalt auf den bereitgestellten Canvas. Das System stellt einen Canvas bereit, der Befehle in Bitmap oder GPU übersetzt.

Der vollständige View Lifecycle-Zyklus umfasst auch Methoden zum Anhängen einer View an ein Fenster: onAttachedToWindow (View ist an ein Fenster angehängt, hat HW-Beschleunigung) und onDetachedFromWindow (View ist getrennt, Ressourcen werden freigegeben). Diese Methoden werden einmal pro View-Lebensdauer aufgerufen und sind wichtig zum Registrieren/Abbestellen von Animationen und Sensoren.

Laut Android Performance Blog stehen 65% der UI-Leistungsprobleme (Ruckeln, Frame-Ausfälle) im Zusammenhang mit falscher Implementierung von onMeasure und onDraw: übermäßiges Überschreiben, unnötiges Aufrufen von requestLayout(), Erstellen von Objekten in onDraw.

onMeasure: Messen der View-Abmessungen

onMeasure — die wichtigste und komplexeste Phase des View Lifecycle. In dieser Phase bestimmt Android, wie viel Platz die View auf dem Bildschirm einnehmen wird. Das System übergibt MeasureSpec — in int gepackte Anweisungen, bestehend aus einem Modus und einer Größe.

Die drei MeasureSpec-Modi:

ModusKonstanteBedeutungBeispiel
EXACTLYMeasureSpec.EXACTLYGenauer, vom Eltern festgelegter Wert (match_parent oder feste Breite)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView kann bis zur angegebenen Maximalgröße haben (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDKeine Einschränkungen — View kann beliebige Größe haben (ScrollView, RecyclerView)Breite unbegrenzt → MeasureSpec(0, UNSPECIFIED)

Die Implementierung von onMeasure sollte:

  • setMeasuredDimension(int width, int height) aufrufen, um die gemessenen Abmessungen zu speichern.
  • Padding berücksichtigen — getPaddingLeft() + getPaddingRight() von der verfügbaren Breite abziehen.
  • Für ViewGroup — alle Kinder via measureChild() oder measureChildWithMargins() messen.
  • Für wrap_content — Größe basierend auf Inhalt (Text, Bild) berechnen.
  • Kein requestLayout() innerhalb von onMeasure aufrufen — dies würde eine Endlosschleife verursachen.

Typischer Fehler: MeasureSpec bei wrap_content nicht berücksichtigen. Wenn eine View auf wrap_content gesetzt ist, aber onMeasure AT_MOST nicht behandelt und eine feste Größe zurückgibt, wird die View entweder abgeschnitten oder nimmt mehr Platz als nötig ein.

onLayout: Platzieren von Views auf dem Bildschirm

onLayout — die Phase, in der eine View oder ViewGroup ihre Kinder anordnet innerhalb ihrer Grenzen. Für eine normale View (keine ViewGroup) ist onLayout nicht erforderlich — das System ruft layout() mit den vom Eltern übergebenen Parametern auf. Für eine ViewGroup ist onLayout obligatorisch — ohne es werden untergeordnete Views nicht platziert.

Signatur von onLayout:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // Anordnung untergeordneter Views
}

Der Parameter changed gibt an, ob sich die Position oder Größe der View im Vergleich zum vorherigen Layout geändert hat. Bei false kann die View zur Optimierung die Neuberechnung der Kindpositionen überspringen.

Für ViewGroup sollte onLayout:

  • Alle Kinder via getChildCount() und getChildAt(i) durchlaufen.
  • Für jedes Kind left, top, right, bottom bestimmen — Koordinaten innerhalb der ViewGroup (unter Berücksichtigung von Padding).
  • child.layout(l, t, r, b) für jedes Kind aufrufen.
  • Gravity, Margins, Alignment berücksichtigen.

onLayout wird nach onMeasure aufgerufen — gemessene Abmessungen sind via getMeasuredWidth()/getMeasuredHeight() verfügbar. Wenn eine untergeordnete View nach layout() andere tatsächliche Abmessungen hat, wird requestLayout() zur erneuten Messung aufgerufen. Dies wird als „Layout-Pass“ bezeichnet und kann eine Kettenreaktion von Neuberechnungen auslösen.

onDraw: Zeichnen von Canvas-Inhalten

onDraw — die Phase, in der eine View sich selbst auf Canvas zeichnet. Dies ist die einzige Phase, die mehrfach ohne onMeasure und onLayout aufgerufen werden kann — wenn die View als invalidate() markiert ist. Canvas bietet eine Zeichen-API: drawLine, drawRect, drawCircle, drawText, drawBitmap und drawPath.

onDraw-Regeln:

  • Keine Objekte in onDraw erstellen — jeder onDraw-Aufruf sollte vorerstellte Objekte (Path, Paint, Rect) verwenden. Das Erstellen von Objekten in onDraw verursacht GC-Pausen und verworfene Frames.
  • Kein requestLayout() oder invalidate() in onDraw aufrufen — dies löst eine Endlosschleife des Neuzeichnens aus.
  • Keine langen Berechnungen durchführen — onDraw wird im UI-Thread ausgeführt. Komplexe Berechnungen sollten in einen Hintergrundthread verschoben oder vorab berechnet werden.
  • Hardwarebeschleunigung verwenden — ab API 14+ kann Canvas über GPU arbeiten. Bei komplexen Grafiken (Verläufe, Schatten, Drehungen) bietet HW-Beschleunigung bis zu 300% Leistungssteigerung.
  • Nur den sichtbaren Bereich zeichnencanvas.clipRect() zum Beschneiden unsichtbarer Teile verwenden.

Zeichenreihenfolge in ViewGroup: Hintergrund (setBackgroundDrawable) → onDraw (Inhalt) → dispatchDraw (untergeordnete Views) → onDrawForeground (Vordergrund). dispatchDraw ruft onDraw jedes Kindes auf. Das Überschreiben von dispatchDraw wird verwendet, um Effekte über untergeordneten Elementen anzuwenden.

Laut Android Vitals-Statistiken sind die häufigsten Ursachen für Frame-Ausfälle in onDraw das Erstellen von Objekten innerhalb der Methode (48%), das Aufrufen von decodeResource (22%) und komplexe Path-Operationen ohne Caching (15%).

Invalidierung: Wann eine View neu gezeichnet wird

Invalidierung — der Mechanismus, der das Neuzeichnen der View auslöst. Der Aufruf von invalidate() markiert die View als „schmutzig“ und plant den Aufruf von onDraw im nächsten Zeichenzyklus. Der Aufruf von requestLayout() ist eine „schwerere“ Operation, die den vollständigen Zyklus auslöst: onMeasure → onLayout → onDraw.

MethodeFunktionVerwendungszweck
invalidate()Löst onDraw ohne onMeasure/onLayout ausNur das Aussehen hat sich geändert (Farbe, Text, Fortschritt)
invalidate(Rect)Zeichnet nur den angegebenen Bereich neuTeil der View hat sich geändert — Animation, Auswahl
postInvalidate()Ruft invalidate von einem Nicht-UI-Thread aufHintergrundthread hat Daten zum Zeichnen aktualisiert
requestLayout()Löst onMeasure → onLayout → onDraw ausDie Inhaltsgröße hat sich geändert (Text, Bild)
forceLayout()Markiert View zur erzwungenen NeuvermessungInterner Zustand geändert, Größe könnte sich geändert haben

Animationen und View Lifecycle: ViewPropertyAnimator und ValueAnimator rufen invalidate() in jedem Animationsframe auf. ObjectAnimator ruft einen Setter auf der View auf, der, wenn der Setter die Größe (Breite/Höhe) ändert, automatisch requestLayout() aufruft. Dies kann für komplexe ViewGroups teuer sein: Jede requestLayout löst die gesamte Hierarchie bis zur Root-View aus.

Optimierungsregel: invalidate() statt requestLayout() überall dort, wo sich nur das Aussehen ändert (Farbe, Transparenz, Drehung ohne Größenänderung). Verwenden Sie requestLayout nur beim Ändern von Größen oder Inhalten, die die Größe beeinflussen.

Optimierung benutzerdefinierter Views: Best Practices

Benutzerdefinierte Views sind ein leistungsstarkes Werkzeug zur Erstellung einzigartiger UIs, erfordern jedoch die strikte Einhaltung von Leistungsregeln. Hier sind die wichtigsten Empfehlungen von Google zur Optimierung des View Lifecycle.

  • Alles vorberechnen, was berechnet werden kann — Größen, Koordinaten, Pfad, Verlaufsfarben. In onDraw nur zeichnen.
  • Messergebnisse cachen — wenn eine View feste Abmessungen hat, MeasureSpec speichern und setMeasuredDimension ohne zusätzliche Berechnungen zurückgeben.
  • ViewConfiguration verwenden — getScaledTouchSlop, getScaledMinimumFlingVelocity — für die Touch-Behandlung.
  • Anzahl der Views in der Hierarchie minimieren — benutzerdefinierte Views, die mehrere Elemente kombinieren, sind immer schneller als eine ViewGroup mit 3–5 verschachtelten Views. Google empfiehlt nicht mehr als 10 verschachtelte Views pro Bildschirm.
  • ConstraintLayout für flache Hierarchie verwenden — es baut eine einzelne ViewGroup mit Leistung nahe an RelativeLayout, aber ohne Verschachtelung.
  • Hardware-Layer nach dem Neuzeichnen deaktivierensetLayerType(LAYER_TYPE_HARDWARE) für Views mit Animationen und setLayerType(LAYER_TYPE_NONE) nach Abschluss verwenden.
  • invalidate() mit Rect verwenden — nur den geänderten Bereich neu zeichnen, nicht die gesamte View.
  • Overdraw vermeiden — Profile GPU Rendering in Android Studio verwenden, um unnötiges Neuzeichnen zu identifizieren. Der durchschnittliche Overdraw für Google-Apps beträgt 1.5x, das empfohlene Maximum ist 2.5x.

View-Codebeispiele in Kotlin

Beispiel 1: Benutzerdefinierte View — Fortschrittsanzeige

Eine einfache kreisförmige Fortschrittsanzeige mit korrekter Implementierung von onMeasure, onDraw und invalidate.

kotlin
class CircularProgressView constructor(
    context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {

    private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        style = Paint.Style.STROKE
        strokeWidth = 8f
        strokeCap = Paint.Cap.ROUND
    }

    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.LTGRAY
        style = Paint.Style.STROKE
        strokeWidth = 8f
    }

    private var progress = 0f
    private var viewWidth = 0
    private var viewHeight = 0

    fun setProgress(value: Float) {
        progress = value.coerceIn(0f, 100f)
        invalidate()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val desiredSize = 100 * resources.displayMetrics.density.toInt()
        val width = MeasureSpec.getSize(widthMeasureSpec)
        val height = MeasureSpec.getSize(heightMeasureSpec)
        val size = minOf(width, height).coerceAtLeast(desiredSize)
        setMeasuredDimension(size, size)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val padding = progressPaint.strokeWidth / 2
        val radius = (minOf(viewWidth, viewHeight) - padding) / 2
        val cx = viewWidth / 2f
        val cy = viewHeight / 2f
        canvas.drawCircle(cx, cy, radius, backgroundPaint)
        val sweepAngle = (progress / 100f) * 360f
        canvas.drawArc(cx - radius, cy - radius, cx + radius, cy + radius,
            -90f, sweepAngle, false, progressPaint)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = w
        viewHeight = h
    }
}

Kreisförmige Fortschrittsanzeige: onMeasure gibt eine quadratische Größe basierend auf MeasureSpec zurück, onSizeChanged merkt sich die Abmessungen, onDraw zeichnet den Hintergrund und den Fortschrittsbogen. Invalidate wird bei Änderung des Fortschritts aufgerufen — onMeasure/onLayout bleiben unbeeinflusst. Paint wird einmal im Konstruktor erstellt, nicht in onDraw.

Beispiel 2: ViewGroup — einfaches FlowLayout

Eine benutzerdefinierte ViewGroup, die untergeordnete Views in Zeilen anordnet (wie Flexbox wrap).

kotlin
class FlowLayout constructor(
    context: Context, attrs: AttributeSet? = null
) : ViewGroup(context, attrs) {

    private val horizontalSpacing = 8.dpToPx(resources)
    private val verticalSpacing = 8.dpToPx(resources)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val width = MeasureSpec.getSize(widthMeasureSpec)
        var totalHeight = paddingTop + paddingBottom
        var rowWidth = paddingLeft
        var rowHeight = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight)
            if (rowWidth + child.measuredWidth > width - paddingRight) {
                totalHeight += rowHeight + verticalSpacing
                rowWidth = paddingLeft
                rowHeight = 0
            }
            rowWidth += child.measuredWidth + horizontalSpacing
            rowHeight = maxOf(rowHeight, child.measuredHeight)
        }
        totalHeight += rowHeight
        setMeasuredDimension(
            MeasureSpec.getSize(widthMeasureSpec),
            resolveSize(totalHeight, heightMeasureSpec)
        )
    }

    override fun onLayout(changed: Boolean,
        l: Int, t: Int, r: Int, b: Int) {
        var rowTop = paddingTop
        var rowLeft = paddingLeft
        var rowHeight = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (rowLeft + child.measuredWidth > r - paddingRight) {
                rowTop += rowHeight + verticalSpacing
                rowLeft = paddingLeft
                rowHeight = 0
            }
            child.layout(rowLeft, rowTop, rowLeft + child.measuredWidth, rowTop + child.measuredHeight)
            rowLeft += child.measuredWidth + horizontalSpacing
            rowHeight = maxOf(rowHeight, child.measuredHeight)
        }
    }

    override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
        return MarginLayoutParams(context, attrs)
    }
}

FlowLayout überschreibt onMeasure: misst jedes Kind, bricht bei Überschreitung der Breite in eine neue Zeile um, berechnet die Gesamthöhe. onLayout positioniert Kinder nach Koordinaten unter Berücksichtigung von Zeilenumbrüchen. generateLayoutParams gibt MarginLayoutParams zurück, um Margin bei untergeordneten Views zu unterstützen.

Beispiel 3: onDraw mit Path-Caching

Eine benutzerdefinierte View zeichnet eine glatte Bezier-Kurve, berechnet den Path vorab und cached ihn.

kotlin
class WaveView constructor(
    context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {

    private val wavePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#4A90D9")
        style = Paint.Style.FILL
    }

    private val wavePath = Path()
    private var isPathDirty = true
    private var viewWidth = 0
    private var viewHeight = 0

    fun refreshWave() {
        isPathDirty = true
        invalidate()
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        viewWidth = w
        viewHeight = h
        isPathDirty = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (isPathDirty) {
            wavePath.reset()
            val amplitude = viewHeight * 0.1f
            wavePath.moveTo(0f, viewHeight * 0.5f)
            for (x in 0..viewWidth step 4) {
                val y = viewHeight * 0.5f + amplitude * Math.sin(x * 2 * Math.PI / viewWidth).toFloat()
                wavePath.lineTo(x.toFloat(), y)
            }
            wavePath.lineTo(viewWidth.toFloat(), viewHeight.toFloat())
            wavePath.lineTo(0f, viewHeight.toFloat())
            wavePath.close()
            isPathDirty = false
        }
        canvas.drawPath(wavePath, wavePaint)
    }
}

Path-Caching: isPathDirty = true nur bei Änderung der View-Abmessungen oder Aufruf von refreshWave(). In onDraw wird Path nur neu berechnet, wenn er „schmutzig“ ist. Dies verhindert die Neuberechnung der Bezier-Kurve in jedem Animationsframe und spart CPU.

Häufig gestellte Fragen

Wie unterscheidet sich View Lifecycle von Activity Lifecycle?

View Lifecycle ist ein zyklischer Zeichenprozess (onMeasure → onLayout → onDraw), unabhängig von der Erstellung/Zerstörung der Activity. View hat kein onStart/onStop — es ist entweder sichtbar (an ein Fenster angehängt) oder nicht. Activity Lifecycle verwaltet den Zustand der Anwendungskomponente, View Lifecycle verwaltet das UI-Zeichnen.

Welche Auswirkung hat requestLayout() auf die Leistung?

requestLayout() löst den vollständigen onMeasure → onLayout → onDraw-Zyklus für den gesamten View-Baum von der Wurzel aus aus. Bei häufigem Aufruf (z.B. jedem Animationsframe) verursacht es Ruckeln und verworfene Frames. Laut Google dauert ein requestLayout bei einer ViewGroup mit 10 Elementen durchschnittlich 2–5 ms. Für Animationen verwenden Sie invalidate().

Wann wird onAttachedToWindow aufgerufen?

onAttachedToWindow wird aufgerufen, wenn eine View an ein Fenster angehängt wird — Teil der sichtbaren Hierarchie wird. In diesem Moment erhält die View HW-Beschleunigung und Zugriff auf Fensterressourcen (WindowManager, Display). onAttachedToWindow ist der richtige Ort, um Animations-Listener und BroadcastReceiver zu registrieren, die leben, solange die View sichtbar ist.

Was ist Overdraw und wie reduziert man es?

Overdraw ist eine Situation, in der ein Pixel mehrmals in einem einzelnen Frame gezeichnet wird. Jeder zusätzliche Durchgang verschwendet GPU-Zeit. Reduktionsmethoden: windowBackground im Theme setzen (keinen Hintergrund im Layout zeichnen), canvas.clipRect() verwenden, Zusammenführen verschachtelter Hintergründe vermeiden, ConstraintLayout anstelle von verschachteltem LinearLayout verwenden. Android Studio → Profile GPU Rendering → Overdraw zeigt eine Farbkarte des Overdraw (blau = 1x, rot = 3x+).

Ist super.onDraw() in einer benutzerdefinierten View erforderlich?

Ja, wenn die View einen Hintergrund hat. super.onDraw() zeichnet den View-Hintergrund. Wenn Ihre benutzerdefinierte View keinen Hintergrund hat oder Sie einen eigenen Hintergrund zeichnen, kann super.onDraw() weggelassen werden — dies spart einen Zeichendurchgang. Für ViewGroup ist super.dispatchDraw() obligatorisch — es zeichnet die untergeordneten Views.

Zusammenfassung

  • View Lifecycle — drei Zeichenphasen: onMeasure (Abmessungen), onLayout (Position), onDraw (Rendering).
  • onMeasure verarbeitet MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) und ruft setMeasuredDimension auf.
  • onLayout in ViewGroup positioniert untergeordnete Views mit left/top/right/bottom-Koordinaten.
  • onDraw rendert Inhalt auf Canvas — erstellen Sie keine Objekte innerhalb dieser Methode.
  • invalidate() löst nur onDraw aus, requestLayout() löst den vollständigen onMeasure → onLayout → onDraw-Zyklus aus.
  • Benutzerdefinierte Views beschleunigen die UI um 15–40%, erfordern jedoch korrekte onMeasure-Implementierung und Objekt-Caching in onDraw.
  • Für komplexe Grafiken verwenden Sie Hardwarebeschleunigung und cachen Sie Path/Bitmap.

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.

Projekt besprechen

Lesen Sie auch