View Lifecycle — vad är det, processer onMeasure onLayout onDraw

Författare: IT Sectr Publicerad: 2026-03-05 Lästid: 10 min

View Lifecycle — sekvensen av metoder som Android anropar för att rendera och omrendera ett gränssnittselement (View) på skärmen. Till skillnad från Activity eller Fragment är View en lättviktskomponent som inte har en utökad livscykel, men genomgår en strikt trefasprocess: onMeasure (mätning), onLayout (placering), onDraw (ritning). Förståelse av View Lifecycle är nödvändig för att skapa anpassade View, optimera prestanda och lösa renderingsproblem. Enligt Google snabbar anpassade View upp UI med 15–40% jämfört med kombinationer av standard nästlade ViewGroup vid korrekt implementering. Android-dokumentation om anpassade View beskriver onMeasure, onLayout och onDraw som de tre pelarna i View Lifecycle.

Huvudpunkter

  • View Lifecycle består av tre faser: onMeasure (mått), onLayout (positioner), onDraw (ritning) — och utlöses vid invalidate() eller requestLayout().
  • onMeasure beräknar bredd och höjd för View baserat på MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED).
  • onLayout placerar underordnade View inom ViewGroup och bestämmer deras koordinater left, top, right, bottom.
  • onDraw renderar innehållet i View på Canvas: bakgrund, text, former, bilder.
  • Felaktig View Lifecycle — främsta orsaken till UI-prestandaproblem (jank, dropped frames) och hierarki.

View Lifecycle — vad är det i Android

View Lifecycle — processen som Android View (och ViewGroup) genomgår för att visa sig på skärmen. Till skillnad från Activity eller Fragment har View inget onStart/onStop/onDestroy — dess “liv” består av en cyklisk process av mätning, placering och ritning. Denna cykel utlöses varje gång View behöver visas eller ritas om.

Tre faser av View Lifecycle:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — bestämmer önskade mått för View. Systemet skickar MeasureSpec — en instruktion om tillåtna mått (exakt värde, maximum eller utan begränsningar).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — placerar View och dess avkomlingar på skärmen. För View bestämmer det egna gränser, för ViewGroup — positioner för underordnade element.
  • onDraw(Canvas canvas) — ritar innehållet i View på den angivna Canvas. Systemet tillhandahåller Canvas som översätter kommandon till bitmapp eller GPU.

Den fullständiga View Lifecycle-cykeln inkluderar även metoder relaterade till att fästa View vid fönstret: onAttachedToWindow (View är fäst vid fönstret, har HW acceleration) och onDetachedFromWindow (View är lossad, resurser frigörs). Dessa metoder anropas en gång under Viewns livstid och är viktiga för registrering/avbokning av animationer, sensorer.

Enligt Android Performance Blog är 65% av UI-prestandaproblemen (jank, hoppade bildrutor) relaterade till felaktig implementering av onMeasure och onDraw: överdriven överskuggning, anrop av requestLayout() i onödan, skapande av objekt i onDraw.

onMeasure: mätning av View-mått

onMeasure — den viktigaste och mest komplexa fasen av View Lifecycle. I detta skede bestämmer Android hur mycket utrymme View kommer att ta på skärmen. Systemet skickar MeasureSpec — instruktioner packade i int bestående av läge och storlek.

Tre MeasureSpec-lägen:

LägeKonstantBetydelseExempel
EXACTLYMeasureSpec.EXACTLYExakt storlek bestämd av förälder (match_parent eller fast bredd)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView kan vara upp till angivet maximum (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDUtan begränsningar — View kan vara valfri storlek (ScrollView, RecyclerView)bredd obegränsad → MeasureSpec(0, UNSPECIFIED)

Implementering av onMeasure bör:

  • Anropa setMeasuredDimension(int width, int height) för att spara uppmätta mått.
  • Ta hänsyn till padding — dra av getPaddingLeft() + getPaddingRight() från tillgänglig bredd.
  • För ViewGroup — mäta alla avkomlingar via measureChild() eller measureChildWithMargins().
  • För wrap_content — beräkna storlek baserat på innehåll (text, bild).
  • Inte anropa requestLayout() inuti onMeasure — detta skulle orsaka en oändlig loop.

Typiskt fel: att inte ta hänsyn till MeasureSpec vid wrap_content. Om View är inställd på wrap_content, men onMeasure inte bearbetar AT_MOST och returnerar en fast storlek, kommer View antingen att beskuras eller ta mer plats än nödvändigt.

onLayout: placering av View på skärmen

onLayout — fasen där View eller ViewGroup placerar sina avkomlingar inom sina gränser. För en vanlig View (inte ViewGroup) krävs inte onLayout — systemet anropar själv layout() med parametrar från föräldern. För ViewGroup är onLayout obligatoriskt — utan det kommer underordnade View inte att placeras.

Signatur för onLayout:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // placering av underordnade View
}

Parametern changed anger om positionen eller storleken på View har ändrats jämfört med föregående layout. Om false — kan View hoppa över omberäkning av avkomlingars positioner för optimering.

För ViewGroup bör onLayout:

  • Gå igenom alla avkomlingar via getChildCount() och getChildAt(i).
  • För varje avkomling bestämma left, top, right, bottom — koordinater inom ViewGroup (med padding).
  • Anropa child.layout(l, t, r, b) för varje avkomling.
  • Ta hänsyn till gravitation, marginaler, justering.

onLayout anropas efter onMeasure — uppmätta mått är tillgängliga via getMeasuredWidth()/getMeasuredHeight(). Om en underordnad View har andra verkliga mått efter layout(), kommer requestLayout() att anropas för omätning. Detta kallas “layout pass” och kan orsaka en kedjereaktion av omberäkningar.

onDraw: ritning av Canvas-innehåll

onDraw — fasen där View ritar sig själv på Canvas. Detta är den enda fasen som kan anropas flera gånger utan onMeasure och onLayout — om View är markerad som invalidate(). Canvas tillhandahåller API för ritning: drawLine, drawRect, drawCircle, drawText, drawBitmap och drawPath.

Regler för onDraw:

  • Skapa inte objekt i onDraw — varje onDraw-anrop bör använda förskapade objekt (Path, Paint, Rect). Att skapa objekt i onDraw orsakar GC-pauser och hoppade bildrutor.
  • Anropa inte requestLayout() eller invalidate() inuti onDraw — detta skulle utlösa en oändlig omritningsloop.
  • Gör inte långa beräkningar — onDraw körs på UI-tråden. Komplexa beräkningar bör flyttas till bakgrundstråd eller beräknas i förväg.
  • Använd Hardware Acceleration — från API 14+ kan Canvas arbeta via GPU. För komplex grafik (gradienter, skuggor, rotationer) ger HW-acceleration en prestandaökning på upp till 300%.
  • Rita endast det synliga området — använd canvas.clipRect() för att beskära osynliga delar.

Ritningsordning i ViewGroup: bakgrund (setBackgroundDrawable) → onDraw (innehåll) → dispatchDraw (underordnade View) → onDrawForeground (förgrund). dispatchDraw anropar onDraw för varje avkomling. Överskuggning av dispatchDraw används för att applicera effekter ovanpå underordnade element.

Enligt Android Vitals statistik är de vanligaste orsakerna till hoppade bildrutor i onDraw — skapande av objekt inuti metoden (48%), anrop av decodeResource (22%) och komplexa operationer med Path utan cache (15%).

Invalidation: när View ritas om

Invalidation — mekanismen som utlöser omritning av View. Anrop av invalidate() markerar View som “smutsig” och schemalägger anrop av onDraw i nästa ritningscykel. Anrop av requestLayout() — en tyngre operation som utlöser hela cykeln: onMeasure → onLayout → onDraw.

MetodVad den görNär ska användas
invalidate()Anropar onDraw utan onMeasure/onLayoutEndast utseendet har ändrats (färg, text, förlopp)
invalidate(Rect)Ritar endast om det angivna områdetEn del av View har ändrats — animation, markering
postInvalidate()Anropar invalidate från icke-UI-trådBakgrundstråd har uppdaterat data för ritning
requestLayout()Utlöser onMeasure → onLayout → onDrawInnehållets storlek har ändrats (text, bild)
forceLayout()Markerar View för tvångsomätningInternt tillstånd har ändrats, storleken kan ha ändrats

Animationer och View Lifecycle: ViewPropertyAnimator och ValueAnimator anropar invalidate() vid varje bildruta i animationen. ObjectAnimator anropar en setter på View som, om settern ändrar storleken (width/height), automatiskt anropar requestLayout(). Detta kan vara kostsamt för komplexa ViewGroup: varje requestLayout utlöser hela hierarkin ända upp till rotvyn.

Optimeringsregel: invalidate() istället för requestLayout() överallt där endast utseendet ändras (färg, transparens, rotation utan storleksändring). Använd requestLayout endast vid ändring av mått eller innehåll som påverkar storleken.

Optimering av anpassade View: bästa praxis

Anpassade View — ett kraftfullt verktyg för att skapa unikt UI, men kräver strikt efterlevnad av prestandaregler. Här är Googles viktigaste rekommendationer för optimering av View Lifecycle.

  • Beräkna i förväg allt som kan beräknas — mått, koordinater, Path-sökväg, gradientfärger. I onDraw använd endast ritning.
  • Cachea mätresultatet — om View har fasta mått, spara MeasureSpec och returnera setMeasuredDimension utan ytterligare beräkningar.
  • Använd ViewConfiguration — getScaledTouchSlop, getScaledMinimumFlingVelocity — för hantering av beröring.
  • Minimera antalet View i hierarkin — anpassade View som kombinerar flera element är alltid snabbare än en ViewGroup med 3–5 nästlade View. Google rekommenderar inte mer än 10 nästlade View på en skärm.
  • Använd ConstraintLayout för platt hierarki — bygger en enda ViewGroup med prestanda nära RelativeLayout, men utan nästling.
  • Stäng av hårdvarulager vid omritning — använd setLayerType(LAYER_TYPE_HARDWARE) för View med animationer och setLayerType(LAYER_TYPE_NONE) efter slutförande.
  • Använd invalidate() med Rect — rita endast om det ändrade området, inte hela View.
  • Undvik overdraw — använd Profile GPU Rendering i Android Studio för att upptäcka onödig ritning. Genomsnittlig overdraw för Google-appar — 1.5x, maximal rekommenderad — 2.5x.

Kodexempel för View i Kotlin

Exempel 1: Anpassad View — förloppsindikator

Enkel cirkulär förloppsindikator med korrekt implementering av onMeasure, onDraw och 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
    }
}

Cirkulär förloppsindikator: onMeasure returnerar kvadratisk storlek baserat på MeasureSpec, onSizeChanged kommer ihåg måtten, onDraw ritar bakgrund och förloppsbåge. Invalidate anropas vid förloppsändring — onMeasure/onLayout påverkas inte. Paint skapas en gång i konstruktorn, inte i onDraw.

Exempel 2: ViewGroup — enkel FlowLayout

Anpassad ViewGroup som placerar underordnade View i rader (som 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 överskuggar onMeasure: mäter varje avkomling, flyttar till ny rad vid överskridning av bredd, beräknar total höjd. onLayout placerar barnen efter koordinater med hänsyn till radbrytningar. generateLayoutParams returnerar MarginLayoutParams för stöd av marginaler hos underordnade View.

Exempel 3: onDraw med Path-cache

Anpassad View ritar en mjuk Bezier-kurva, beräknar Path i förväg och cachar den.

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-cache: isPathDirty = true endast vid ändring av View-mått eller anrop av refreshWave(). I onDraw beräknas Path om endast om det är “smutsigt”. Detta förhindrar omberäkning av Bezier-kurvan vid varje animationsbildruta, vilket sparar CPU.

Vanliga frågor

Hur skiljer sig View Lifecycle från Activity Lifecycle?

View Lifecycle — en cyklisk ritningsprocess (onMeasure → onLayout → onDraw), oberoende av skapande/förstöring av Activity. View har inget onStart/onStop — den är antingen synlig (fäst vid fönstret) eller inte. Activity Lifecycle hanterar tillståndet för applikationskomponenten, View Lifecycle — ritningen av UI.

Vilken påverkan har requestLayout() på prestanda?

requestLayout() utlöser hela cykeln onMeasure → onLayout → onDraw för hela View-trädet från roten. Om requestLayout() anropas ofta (t.ex. varje bildruta i en animation) orsakar det jank och hoppade bildrutor. Enligt Google tar en requestLayout i genomsnitt 2–5 ms på en ViewGroup med 10 element. För animationer, använd invalidate().

När anropas onAttachedToWindow?

onAttachedToWindow anropas när View fästs vid fönstret (Window) — blir en del av den synliga hierarkin. I detta ögonblick får View HW-acceleration och tillgång till fönstrets resurser (WindowManager, Display). onAttachedToWindow är rätt plats för att registrera animationslyssnare och BroadcastReceiver som lever medan View är synlig.

Vad är overdraw och hur minskar man det?

Overdraw — en situation där en pixel ritas flera gånger i en enda bildruta. Varje extra genomgång är slöseri med GPU-tid. Reduktionsmetoder: ställ in windowBackground i theme (rita inte bakgrund i layout), använd canvas.clipRect(), undvik sammanslagning av nästlade bakgrunder, använd ConstraintLayout istället för nästlade LinearLayout. Android Studio → Profile GPU Rendering → Overdraw visar en färgkarta över overdraw (blå = 1x, röd = 3x+).

Behövs super.onDraw() i en anpassad View?

Ja, om View har bakgrund (background). super.onDraw() ritar bakgrunden för View. Om din anpassade View inte har någon bakgrund eller du ritar din egen bakgrund, kan super.onDraw() utelämnas — detta sparar en ritningsgenomgång. För ViewGroup är super.dispatchDraw() obligatoriskt — det ritar underordnade View.

Sammanfattning

  • View Lifecycle — tre ritningsfaser: onMeasure (mått), onLayout (position), onDraw (ritning).
  • onMeasure bearbetar MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) och anropar setMeasuredDimension.
  • onLayout i ViewGroup placerar underordnade View med koordinater left/top/right/bottom.
  • onDraw ritar innehåll på Canvas — skapa inte objekt inuti denna metod.
  • invalidate() anropar endast onDraw, requestLayout() — hela cykeln onMeasure → onLayout → onDraw.
  • Anpassade View snabbar upp UI med 15–40%, men kräver korrekt implementering av onMeasure och objektcache i onDraw.
  • För komplex grafik, använd Hardware Acceleration och cacha Path/Bitmap.

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också