View Lifecycle — wat is het, processen onMeasure onLayout onDraw

Auteur: IT Sectr Gepubliceerd: 2026-03-05 Leestijd: 10 min

View Lifecycle — de reeks methoden die Android aanroept om een gebruikersinterface-element (View) op het scherm weer te geven en opnieuw weer te geven. In tegenstelling tot Activity of Fragment is View een lichtgewicht component zonder uitgebreide levenscyclus, maar doorloopt het een strikt driefasenproces: onMeasure (meten), onLayout (plaatsen), onDraw (tekenen). Inzicht in View Lifecycle is noodzakelijk voor het maken van aangepaste Views, prestatie-optimalisatie en het oplossen van weergaveproblemen. Volgens Google versnellen aangepaste Views de UI met 15–40% in vergelijking met een combinatie van standaard geneste ViewGroups bij correcte implementatie. Android-documentatie over aangepaste Views beschrijft onMeasure, onLayout en onDraw als de drie pijlers van View Lifecycle.

Belangrijkste punten

  • View Lifecycle bestaat uit drie fasen: onMeasure (afmetingen), onLayout (posities), onDraw (tekenen) — en wordt gestart bij invalidate() of requestLayout().
  • onMeasure berekent de breedte en hoogte van View op basis van MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED).
  • onLayout plaatst onderliggende Views binnen ViewGroup en bepaalt hun coördinaten left, top, right, bottom.
  • onDraw tekent de inhoud van View op Canvas: achtergrond, tekst, vormen, afbeeldingen.
  • Onjuiste View Lifecycle — belangrijkste oorzaak van UI-prestatieproblemen (jank, dropped frames) en hiërarchie.

View Lifecycle — wat is het in Android

View Lifecycle — het proces dat Android View (en ViewGroup) doorloopt om zichzelf op het scherm weer te geven. In tegenstelling tot Activity of Fragment heeft View geen onStart/onStop/onDestroy — zijn „leven” bestaat uit een cyclisch proces van meten, plaatsen en tekenen. Deze cyclus wordt elke keer gestart wanneer View moet worden weergegeven of opnieuw getekend.

Drie fasen van View Lifecycle:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — bepaalt de gewenste afmetingen van View. Het systeem geeft MeasureSpec door — een instructie over toegestane afmetingen (exacte waarde, maximum of onbeperkt).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — plaatst View en zijn afstammelingen op het scherm. Voor View bepaalt het zijn eigen grenzen, voor ViewGroup — posities van onderliggende elementen.
  • onDraw(Canvas canvas) — tekent de inhoud van View op de meegegeven Canvas. Het systeem levert Canvas dat opdrachten vertaalt naar bitmap of GPU.

De volledige View Lifecycle-cyclus omvat ook methoden gerelateerd aan het hechten van View aan het venster: onAttachedToWindow (View is aan venster gehecht, heeft HW acceleration) en onDetachedFromWindow (View is losgemaakt, bronnen worden vrijgegeven). Deze methoden worden één keer in het leven van View aangeroepen en zijn belangrijk voor het registreren/annuleren van animaties, sensoren.

Volgens de Android Performance Blog is 65% van de UI-prestatieproblemen (jank, overgeslagen frames) gerelateerd aan onjuiste implementatie van onMeasure en onDraw: overmatig overschrijven, onnodig aanroepen van requestLayout(), objecten maken in onDraw.

onMeasure: afmetingen van View meten

onMeasure — de belangrijkste en meest complexe fase van View Lifecycle. In deze fase bepaalt Android hoeveel ruimte View op het scherm inneemt. Het systeem geeft MeasureSpec door — in int verpakte instructies bestaande uit modus en grootte.

Drie modi van MeasureSpec:

ModusConstanteBetekenisVoorbeeld
EXACTLYMeasureSpec.EXACTLYExacte grootte bepaald door ouder (match_parent of vaste breedte)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView kan grootte hebben tot opgegeven maximum (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDGeen beperkingen — View kan elke grootte hebben (ScrollView, RecyclerView)breedte onbeperkt → MeasureSpec(0, UNSPECIFIED)

Implementatie van onMeasure moet:

  • setMeasuredDimension(int width, int height) aanroepen om gemeten afmetingen op te slaan.
  • Padding in acht nemen — getPaddingLeft() + getPaddingRight() aftrekken van beschikbare breedte.
  • Voor ViewGroup — alle afstammelingen meten via measureChild() of measureChildWithMargins().
  • Voor wrap_content — grootte berekenen op basis van inhoud (tekst, afbeelding).
  • Geen requestLayout() aanroepen binnen onMeasure — dit veroorzaakt een oneindige lus.

Typische fout: geen rekening houden met MeasureSpec bij wrap_content. Als View is ingesteld op wrap_content, maar onMeasure verwerkt AT_MOST niet en retourneert een vaste grootte, wordt View ofwel bijgesneden, of neemt het meer ruimte in dan nodig.

onLayout: View op het scherm plaatsen

onLayout — de fase waarin View of ViewGroup zijn afstammelingen plaatst binnen zijn grenzen. Voor een gewone View (niet ViewGroup) is onLayout niet vereist — het systeem roept zelf layout() aan met parameters van de ouder. Voor ViewGroup is onLayout verplicht — zonder worden onderliggende Views niet geplaatst.

Handtekening van onLayout:

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

Parameter changed geeft aan of de positie of grootte van View is veranderd ten opzichte van de vorige layout. Indien false — kan View het herberekenen van posities van afstammelingen overslaan voor optimalisatie.

Voor ViewGroup moet onLayout:

  • Alle afstammelingen doorlopen via getChildCount() en getChildAt(i).
  • Voor elke afstammeling left, top, right, bottom bepalen — coördinaten binnen ViewGroup (met padding).
  • child.layout(l, t, r, b) aanroepen voor elke afstammeling.
  • Gravity, marges, uitlijning in acht nemen.

onLayout wordt aangeroepen na onMeasure — gemeten afmetingen zijn beschikbaar via getMeasuredWidth()/getMeasuredHeight(). Als een onderliggende View andere werkelijke afmetingen heeft na layout(), wordt requestLayout() aangeroepen voor hermeting. Dit wordt een „layout pass” genoemd en kan een kettingreactie van herberekeningen veroorzaken.

onDraw: inhoud van Canvas tekenen

onDraw — de fase waarin View zichzelf tekent op Canvas. Dit is de enige fase die meerdere keren kan worden aangeroepen zonder onMeasure en onLayout — als View is gemarkeerd als invalidate(). Canvas biedt API voor tekenen: drawLine, drawRect, drawCircle, drawText, drawBitmap en drawPath.

Regels voor onDraw:

  • Maak geen objecten in onDraw — elke onDraw-aanroep moet vooraf gemaakte objecten gebruiken (Path, Paint, Rect). Objecten maken in onDraw veroorzaakt GC-pauzes en overgeslagen frames.
  • Roep requestLayout() of invalidate() niet aan binnen onDraw — dit start een oneindige hertekenlus.
  • Doe geen lange berekeningen — onDraw wordt uitgevoerd op de UI-thread. Complexe berekeningen moeten naar een achtergrondthread worden verplaatst of vooraf worden berekend.
  • Gebruik Hardware Acceleration — vanaf API 14+ kan Canvas via GPU werken. Voor complexe grafische elementen (verlopen, schaduwen, rotaties) geeft HW-versnelling een prestatieverbetering tot 300%.
  • Teken alleen het zichtbare gebied — gebruik canvas.clipRect() om onzichtbare delen af te snijden.

Tekenvolgorde in ViewGroup: achtergrond (setBackgroundDrawable) → onDraw (inhoud) → dispatchDraw (onderliggende Views) → onDrawForeground (voorgrond). dispatchDraw roept onDraw van elke afstammeling aan. Het overschrijven van dispatchDraw wordt gebruikt om effecten over onderliggende elementen aan te brengen.

Volgens Android Vitals-statistieken zijn de meest voorkomende oorzaken van overgeslagen frames in onDraw — objecten maken binnen de methode (48%), aanroep van decodeResource (22%) en complexe bewerkingen met Path zonder caching (15%).

Invalidation: wanneer View opnieuw wordt getekend

Invalidation — het mechanisme dat het hertekenen van View activeert. Aanroep van invalidate() markeert View als „vuil” en plant een onDraw-aanroep in de volgende tekencyclus. Aanroep van requestLayout() — een zwaardere bewerking die de volledige cyclus start: onMeasure → onLayout → onDraw.

MethodeWat het doetWanneer gebruiken
invalidate()Roept onDraw aan zonder onMeasure/onLayoutAlleen uiterlijk veranderd (kleur, tekst, voortgang)
invalidate(Rect)Hertekent alleen het opgegeven gebiedEen deel van View veranderd — animatie, selectie
postInvalidate()Roept invalidate aan vanuit niet-UI-threadAchtergrondthread heeft tekengegevens bijgewerkt
requestLayout()Start onMeasure → onLayout → onDrawInhoudsgrootte veranderd (tekst, afbeelding)
forceLayout()Markeert View voor geforceerde hermetingInterne status veranderd, grootte kan zijn gewijzigd

Animaties en View Lifecycle: ViewPropertyAnimator en ValueAnimator roepen invalidate() aan op elk fram van de animatie. ObjectAnimator roept een setter aan op View die, als de setter de grootte (width/height) wijzigt, automatisch requestLayout() aanroept. Dit kan kostbaar zijn voor complexe ViewGroups: elke requestLayout start de volledige hiërarchie tot aan de root-view.

Optimalisatieregel: invalidate() in plaats van requestLayout() overal waar alleen het uiterlijk verandert (kleur, transparantie, rotatie zonder groottewijziging). Gebruik requestLayout alleen bij wijziging van afmetingen of inhoud die de grootte beïnvloedt.

Optimalisatie van aangepaste Views: best practices

Aangepaste Views — een krachtig hulpmiddel voor het creëren van unieke UI, maar vereisen strikte naleving van prestatieregels. Hier zijn de belangrijkste aanbevelingen van Google voor het optimaliseren van View Lifecycle.

  • Bereken alles van tevoren wat berekend kan worden — afmetingen, coördinaten, het Path, verloopkleuren. Gebruik in onDraw alleen tekenen.
  • Cache het meetresultaat — als View vaste afmetingen heeft, bewaar MeasureSpec en retourneer setMeasuredDimension zonder extra berekeningen.
  • Gebruik ViewConfiguration — getScaledTouchSlop, getScaledMinimumFlingVelocity — voor het verwerken van aanrakingen.
  • Minimaliseer het aantal Views in de hiërarchie — aangepaste Views die meerdere elementen combineren zijn altijd sneller dan een ViewGroup met 3–5 geneste Views. Google raadt niet meer dan 10 geneste Views per scherm aan.
  • Gebruik ConstraintLayout voor een platte hiërarchie — bouwt een enkele ViewGroup met prestaties dicht bij RelativeLayout, maar zonder nesting.
  • Schakel de hardwarelaag uit bij hertekenen — gebruik setLayerType(LAYER_TYPE_HARDWARE) voor Views met animaties en setLayerType(LAYER_TYPE_NONE) na voltooiing.
  • Gebruik invalidate() met Rect — herteken alleen het gewijzigde gebied, niet de hele View.
  • Vermijd overdraw — gebruik Profile GPU Rendering in Android Studio om overbodig tekenen op te sporen. Gemiddelde overdraw voor Google-apps — 1.5x, maximaal aanbevolen — 2.5x.

Voorbeelden van View-code in Kotlin

Voorbeeld 1: Aangepaste View — voortgangsindicator

Eenvoudige cirkelvormige voortgangsindicator met correcte implementatie van onMeasure, onDraw en 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
    }
}

Cirkelvormige voortgangsbalk: onMeasure retourneert een vierkante grootte op basis van MeasureSpec, onSizeChanged onthoudt afmetingen, onDraw tekent de achtergrond en de voortgangsboog. Invalidate wordt aangeroepen bij wijziging van voortgang — onMeasure/onLayout worden niet beïnvloed. Paint wordt één keer in de constructor gemaakt, niet in onDraw.

Voorbeeld 2: ViewGroup — eenvoudige FlowLayout

Aangepaste ViewGroup die onderliggende Views in rijen plaatst (zoals 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 overschrijft onMeasure: meet elke afstammeling, gaat naar een nieuwe rij bij overschrijding van de breedte, berekent de totale hoogte. onLayout plaatst kinderen op coördinaten met inachtneming van regeleinden. generateLayoutParams retourneert MarginLayoutParams voor ondersteuning van marges bij onderliggende Views.

Voorbeeld 3: onDraw met Path-caching

Aangepaste View tekent een vloeiende Bezier-curve, berekent Path van tevoren en cacht het.

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 alleen bij wijziging van View-afmetingen of aanroep van refreshWave(). In onDraw wordt Path alleen herberekend als het „vuil” is. Dit voorkomt herberekening van de Bezier-curve op elk animatieframe, wat CPU bespaart.

Veelgestelde vragen

Waarin verschilt View Lifecycle van Activity Lifecycle?

View Lifecycle — een cyclisch tekenproces (onMeasure → onLayout → onDraw), onafhankelijk van het maken/vernietigen van Activity. View heeft geen onStart/onStop — het is ofwel zichtbaar (aan venster gehecht) of niet. Activity Lifecycle beheert de status van de applicatiecomponent, View Lifecycle — het tekenen van de UI.

Wat is het effect van requestLayout() op prestaties?

requestLayout() start de volledige cyclus onMeasure → onLayout → onDraw voor de gehele View-boom vanaf de root. Als requestLayout() vaak wordt aangeroepen (bijvoorbeeld elk animatieframe), veroorzaakt dit jank en overgeslagen frames. Volgens Google duurt één requestLayout gemiddeld 2–5 ms op een ViewGroup met 10 elementen. Gebruik voor animaties invalidate().

Wanneer wordt onAttachedToWindow aangeroepen?

onAttachedToWindow wordt aangeroepen wanneer View aan het venster (Window) wordt gehecht — onderdeel wordt van de zichtbare hiërarchie. Op dat moment krijgt View HW acceleration en toegang tot Window-bronnen (WindowManager, Display). onAttachedToWindow is de juiste plaats voor het registreren van animatie-listeners en BroadcastReceiver die leeft zolang View zichtbaar is.

Wat is overdraw en hoe verminder je het?

Overdraw — een situatie waarin een pixel meerdere keren in één frame wordt getekend. Elke extra doorgang is GPU-tijdverspilling. Reductiemethoden: stel windowBackground in in theme (teken geen achtergrond in layout), gebruik canvas.clipRect(), vermijd het samenvoegen van geneste achtergronden, pas ConstraintLayout toe in plaats van geneste LinearLayouts. Android Studio → Profile GPU Rendering → Overdraw toont een kleurenkaart van overdraw (blauw = 1x, rood = 3x+).

Is super.onDraw() nodig in een aangepaste View?

Ja, als View een achtergrond (background) heeft. super.onDraw() tekent de achtergrond van View. Als uw aangepaste View geen achtergrond heeft of u tekent uw eigen achtergrond, kan super.onDraw() worden weggelaten — dit bespaart één tekenpassage. Voor ViewGroup is super.dispatchDraw() verplicht — het tekent onderliggende Views.

Samenvatting

  • View Lifecycle — drie tekenfasen: onMeasure (afmetingen), onLayout (positie), onDraw (tekenen).
  • onMeasure verwerkt MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) en roept setMeasuredDimension aan.
  • onLayout in ViewGroup plaatst onderliggende Views met coördinaten left/top/right/bottom.
  • onDraw tekent inhoud op Canvas — maak geen objecten binnen deze methode.
  • invalidate() roept alleen onDraw aan, requestLayout() — de volledige cyclus onMeasure → onLayout → onDraw.
  • Aangepaste Views versnellen de UI met 15–40%, maar vereisen correcte implementatie van onMeasure en objectcaching in onDraw.
  • Gebruik voor complexe grafische elementen Hardware Acceleration en cache Path/Bitmap.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook