View Lifecycle — mi ez, onMeasure onLayout onDraw folyamatok

Szerző: IT Sectr Megjelenés: 2026-03-05 Olvasási idő: 10 perc

View Lifecycle — azoknak a metódusoknak a sorozata, amelyeket az Android meghív a felhasználói felület elemének (View) megjelenítéséhez és újramegjelenítéséhez a képernyőn. Az Activity-től vagy Fragment-től eltérően a View egy könnyű komponens, amelynek nincs kiterjesztett életciklusa, de szigorú háromfázisú folyamaton megy keresztül: onMeasure (mérés), onLayout (elhelyezés), onDraw (rajzolás). A View Lifecycle megértése szükséges az egyedi View-ok létrehozásához, a teljesítmény optimalizálásához és a megjelenítési problémák megoldásához. A Google adatai szerint az egyedi View-ok 15–40%-kal gyorsítják az UI-t a szabványos beágyazott ViewGroup kombinációhoz képest helyes implementáció esetén. Az Android dokumentációja az egyedi View-okról az onMeasure, onLayout és onDraw metódusokat írja le a View Lifecycle három pilléreként.

Főbb pontok

  • View Lifecycle három fázisból áll: onMeasure (méretek), onLayout (pozíciók), onDraw (rajzolás) — és invalidate() vagy requestLayout() esetén indul el.
  • Az onMeasure kiszámítja a View szélességét és magasságát a MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED) alapján.
  • Az onLayout elrendezi a gyermek View-okat a ViewGroup-on belül, meghatározva a left, top, right, bottom koordinátáikat.
  • Az onDraw a View tartalmát rajzolja a Canvas-ra: háttér, szöveg, alakzatok, képek.
  • Helytelen View Lifecycle — az UI teljesítményproblémák (jank, kieső képkockák) és hierarchia fő oka.

View Lifecycle — mi ez Androidban

View Lifecycle — az a folyamat, amelyen az Android View (és ViewGroup) átesik, hogy megjelenítse magát a képernyőn. Az Activity-től vagy Fragment-től eltérően a View-nak nincs onStart/onStop/onDestroy — az „élete” a mérés, elhelyezés és rajzolás ciklikus folyamatából áll. Ez a ciklus minden alkalommal elindul, amikor a View-t meg kell jeleníteni vagy újra kell rajzolni.

A View Lifecycle három fázisa:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — meghatározza a View kívánt méreteit. A rendszer átadja a MeasureSpec-et — egy utasítást a megengedett méretekről (pontos érték, maximum vagy korlátozás nélkül).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — elrendezi a View-t és leszármazottait a képernyőn. A View számára meghatározza saját határait, a ViewGroup számára — a gyermek elemek pozícióit.
  • onDraw(Canvas canvas) — kirajzolja a View tartalmát a megadott Canvas-ra. A rendszer biztosítja a Canvas-t, amely a parancsokat bitmap-re vagy GPU-ra fordítja.

A View Lifecycle teljes ciklusa tartalmazza a View ablakhoz csatolásával kapcsolatos metódusokat is: onAttachedToWindow (a View az ablakhoz van csatolva, HW gyorsítással rendelkezik) és onDetachedFromWindow (a View le van választva, az erőforrások felszabadulnak). Ezek a metódusok egyszer hívódnak meg a View élete során, és fontosak az animációk, érzékelők regisztrálásához/lemondásához.

Az Android Performance Blog szerint az UI teljesítményproblémák (jank, képkocka kihagyás) 65%-a az onMeasure és onDraw helytelen megvalósításához kapcsolódik: túlzott felülírás, szükségtelen requestLayout() hívás, objektumok létrehozása az onDraw-ban.

onMeasure: a View méreteinek mérése

onMeasure — a View Lifecycle legfontosabb és legösszetettebb fázisa. Ebben a szakaszban az Android meghatározza, mennyi helyet foglal el a View a képernyőn. A rendszer átadja a MeasureSpec-et — int-be csomagolt utasításokat, amelyek módból és méretből állnak.

A MeasureSpec három módja:

MódKonstansJelentésPélda
EXACTLYMeasureSpec.EXACTLYPontos méret, amelyet a szülő határozott meg (match_parent vagy fix szélesség)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTA View a megadott maximumig terjedő méretű lehet (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDKorlátozás nélkül — a View bármekkora lehet (ScrollView, RecyclerView)szélesség korlátlan → MeasureSpec(0, UNSPECIFIED)

Az onMeasure megvalósításának:

  • Meg kell hívnia a setMeasuredDimension(int width, int height) metódust a mért méretek mentéséhez.
  • Figyelembe kell vennie a padding-et — le kell vonnia a getPaddingLeft() + getPaddingRight() értéket a rendelkezésre álló szélességből.
  • ViewGroup esetén — meg kell mérnie az összes leszármazottat a measureChild() vagy measureChildWithMargins() segítségével.
  • Wrap_content esetén — ki kell számítania a méretet a tartalom alapján (szöveg, kép).
  • Nem szabad requestLayout()-ot hívnia az onMeasure-n belül — ez végtelen ciklust okoz.

Tipikus hiba: a MeasureSpec figyelmen kívül hagyása wrap_content esetén. Ha a View wrap_content-re van állítva, de az onMeasure nem dolgozza fel az AT_MOST értéket és fix méretet ad vissza, a View vagy levágódik, vagy több helyet foglal el, mint amennyi szükséges.

onLayout: View elhelyezése a képernyőn

onLayout — az a fázis, amelyben a View vagy ViewGroup elrendezi leszármazottait a saját határain belül. Egy szokásos View (nem ViewGroup) esetén az onLayout nem szükséges — a rendszer maga hívja meg a layout()-ot a szülőtől kapott paraméterekkel. ViewGroup esetén az onLayout kötelező — nélküle a gyermek View-ok nem lesznek elrendezve.

Az onLayout szignatúrája:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // gyermek View-ok elrendezése
}

A changed paraméter jelzi, hogy a View pozíciója vagy mérete megváltozott-e az előző layout-hoz képest. Ha false — a View kihagyhatja a leszármazottak pozícióinak újraszámítását optimalizálás céljából.

ViewGroup esetén az onLayout-nak:

  • Végig kell mennie az összes leszármazotton a getChildCount() és getChildAt(i) segítségével.
  • Minden leszármazott számára meg kell határoznia a left, top, right, bottom — a ViewGroup-on belüli koordinátákat (padding figyelembevételével).
  • Meg kell hívnia a child.layout(l, t, r, b) metódust minden leszármazott számára.
  • Figyelembe kell vennie a gravity-t, margókat, igazítást.

Az onLayout az onMeasure után hívódik meg — a mért méretek a getMeasuredWidth()/getMeasuredHeight() segítségével érhetők el. Ha egy gyermek View-nak a layout() után eltérő valós méretei vannak, a requestLayout() meghívódik az újraméréshez. Ezt „layout pass”-nak nevezik, és az újraszámítások láncreakcióját okozhatja.

onDraw: a Canvas tartalmának rajzolása

onDraw — az a fázis, amelyben a View lerajzolja magát a Canvas-ra. Ez az egyetlen fázis, amely többször is meghívható onMeasure és onLayout nélkül — ha a View invalidate()-ként van megjelölve. A Canvas API-t biztosít a rajzoláshoz: drawLine, drawRect, drawCircle, drawText, drawBitmap és drawPath.

Az onDraw szabályai:

  • Ne hozzon létre objektumokat az onDraw-ban — minden onDraw hívásnak előre létrehozott objektumokat (Path, Paint, Rect) kell használnia. Objektumok létrehozása az onDraw-ban GC-szüneteket és kieső képkockákat okoz.
  • Ne hívjon requestLayout()-ot vagy invalidate()-et az onDraw-on belül — ez végtelen újrarajzolási ciklust indít el.
  • Ne végezzen hosszú számításokat — az onDraw az UI szálon fut. Az összetett számításokat háttérszálba kell helyezni vagy előre ki kell számolni.
  • Használja a Hardware Acceleration-t — API 14+-tól a Canvas GPU-n keresztül is működhet. Összetett grafikákhoz (gradiensek, árnyékok, forgatások) a HW-gyorsítás akár 300%-os teljesítménynövekedést is biztosít.
  • Csak a látható területet rajzolja — használja a canvas.clipRect() metódust a nem látható részek levágásához.

A rajzolás sorrendje ViewGroup-ban: háttér (setBackgroundDrawable) → onDraw (tartalom) → dispatchDraw (gyermek View-ok) → onDrawForeground (előtér). A dispatchDraw meghívja minden leszármazott onDraw-ját. A dispatchDraw felülírását arra használják, hogy effekteket alkalmazzanak a gyermek elemek felett.

Az Android Vitals statisztikái szerint a kieső képkockák leggyakoribb okai az onDraw-ban — objektumok létrehozása a metóduson belül (48%), a decodeResource hívása (22%), és összetett Path műveletek gyorsítótár nélkül (15%).

Invalidation: mikor rajzolódik újra a View

Invalidation — a View újrarajzolását elindító mechanizmus. A invalidate() hívása „piszkosnak” jelöli a View-t, és ütemezi az onDraw meghívását a következő rajzolási ciklusban. A requestLayout() hívása — egy nehezebb művelet, amely elindítja a teljes ciklust: onMeasure → onLayout → onDraw.

MetódusMit csinálMikor használja
invalidate()Meghívja az onDraw-t onMeasure/onLayout nélkülCsak a megjelenés változott (szín, szöveg, folyamat)
invalidate(Rect)Csak a megadott területet rajzolja újraA View egy része változott — animáció, kijelölés
postInvalidate()Meghívja az invalidate-et nem UI szálbólA háttérszál frissítette a rajzolási adatokat
requestLayout()Elindítja az onMeasure → onLayout → onDraw ciklustA tartalom mérete változott (szöveg, kép)
forceLayout()Kényszerített újramérésre jelöli a View-tA belső állapot változott, a méret változhatott

Animációk és View Lifecycle: A ViewPropertyAnimator és a ValueAnimator minden animációs képkockán meghívja az invalidate()-et. Az ObjectAnimator meghívja a setter-t a View-n, amely ha a setter megváltoztatja a méretet (width/height), automatikusan meghívja a requestLayout()-ot. Ez költséges lehet összetett ViewGroup-ok esetén: minden requestLayout elindítja a teljes hierarchiát a gyökér view-ig.

Optimalizálási szabály: invalidate() a requestLayout() helyett mindenhol, ahol csak a megjelenés változik (szín, átlátszóság, méretváltozás nélküli forgatás). A requestLayout-ot csak a méretek vagy a méretet befolyásoló tartalom változásakor használja.

Egyedi View-ok optimalizálása: legjobb gyakorlatok

Az egyedi View-ok — hatékony eszköz egyedi UI létrehozásához, de szigorúan be kell tartaniuk a teljesítményre vonatkozó szabályokat. Íme a Google legfontosabb ajánlásai a View Lifecycle optimalizálásához.

  • Számítson ki előre mindent, ami kiszámítható — méretek, koordináták, Path útvonal, színátmenet színei. Az onDraw-ban csak rajzolást használjon.
  • Gyorsítótárazza a mérés eredményét — ha a View fix méretekkel rendelkezik, mentse el a MeasureSpec-et és adja vissza a setMeasuredDimension-t további számítások nélkül.
  • Használja a ViewConfiguration-t — getScaledTouchSlop, getScaledMinimumFlingVelocity — az érintések kezeléséhez.
  • Minimalizálja a View-ok számát a hierarchiában — a több elemet egyesítő egyedi View-ok mindig gyorsabbak, mint egy 3–5 beágyazott View-ból álló ViewGroup. A Google legfeljebb 10 beágyazott View-t javasol egy képernyőn.
  • Használja a ConstraintLayout-ot lapos hierarchiához — egyetlen ViewGroup-ot épít a RelativeLayout-hoz közeli teljesítménnyel, de beágyazás nélkül.
  • Kapcsolja ki a hardverréteget újrarajzoláskor — használja a setLayerType(LAYER_TYPE_HARDWARE) beállítást animációkkal rendelkező View-okhoz, és a setLayerType(LAYER_TYPE_NONE) beállítást a befejezés után.
  • Használja az invalidate()-et Rect-tel — csak a megváltozott területet rajzolja újra, ne az egész View-t.
  • Kerülje az overdraw-ot — használja a Profile GPU Rendering-et az Android Studio-ban a felesleges rajzolások észleléséhez. Az átlagos overdraw a Google alkalmazásoknál — 1.5x, a maximális ajánlott — 2.5x.

View kódpéldák Kotlinban

1. példa: Egyedi View — folyamatjelző

Egyszerű kör alakú folyamatjelző az onMeasure, onDraw és invalidate helyes megvalósításával.

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
    }
}

Kör alakú folyamatjelző sáv: az onMeasure négyzet alakú méretet ad vissza a MeasureSpec alapján, az onSizeChanged megjegyzi a méreteket, az onDraw megrajzolja a hátteret és a folyamatívet. Az Invalidate a folyamat változásakor hívódik meg — az onMeasure/onLayout nem érintett. A Paint egyszer, a konstruktorban jön létre, nem az onDraw-ban.

2. példa: ViewGroup — egyszerű FlowLayout

Egyedi ViewGroup, amely a gyermek View-okat sorokba rendezi (mint a 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)
    }
}

A FlowLayout felülírja az onMeasure-t: megméri minden leszármazottat, új sorba lép a szélesség túllépésekor, kiszámítja a teljes magasságot. Az onLayout elrendezi a gyermekeket a koordináták szerint, figyelembe véve a sortöréseket. A generateLayoutParams MarginLayoutParams-t ad vissza a gyermek View-ok margóinak támogatásához.

3. példa: onDraw Path gyorsítótárazással

Az egyedi View sima Bezier-görbét rajzol, előre kiszámítva és gyorsítótárazva a Path-et.

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 gyorsítótárazás: az isPathDirty = true csak a View méreteinek változásakor vagy a refreshWave() meghívásakor. Az onDraw-ban a Path csak akkor számítódik újra, ha „piszkos”. Ez megakadályozza a Bezier-görbe újraszámítását minden animációs képkockán, CPU-t takarítva meg.

Gyakran ismételt kérdések

Miben különbözik a View Lifecycle az Activity Lifecycle-től?

View Lifecycle — ciklikus rajzolási folyamat (onMeasure → onLayout → onDraw), amely független az Activity létrehozásától/megsemmisítésétől. A View-nak nincs onStart/onStop — vagy látható (az ablakhoz csatolva), vagy nem. Az Activity Lifecycle az alkalmazáskomponens állapotát kezeli, a View Lifecycle — az UI rajzolását.

Milyen hatással van a requestLayout() a teljesítményre?

requestLayout() elindítja a teljes onMeasure → onLayout → onDraw ciklust a teljes View-fa számára a gyökértől kezdve. Ha a requestLayout() gyakran hívódik (pl. minden animációs képkockán), ez jank-ot és kieső képkockákat okoz. A Google szerint egy requestLayout átlagosan 2–5 ms-ig tart egy 10 elemből álló ViewGroup esetén. Animációkhoz használja az invalidate()-et.

Mikor hívódik meg az onAttachedToWindow?

onAttachedToWindow akkor hívódik meg, amikor a View csatlakozik az ablakhoz (Window) — a látható hierarchia részévé válik. Ekkor a View HW gyorsítást és hozzáférést kap a Window erőforrásaihoz (WindowManager, Display). Az onAttachedToWindow a megfelelő hely az animációfigyelők és a BroadcastReceiver regisztrálására, amely addig él, amíg a View látható.

Mi az overdraw és hogyan csökkenthető?

Overdraw — az a helyzet, amikor egy pixelt többször rajzolnak meg egyetlen képkockán belül. Minden további áthaladás GPU-idő pazarlás. Csökkentési módszerek: állítsa be a windowBackground értéket a theme-ben (ne rajzoljon hátteret a layout-ban), használja a canvas.clipRect() metódust, kerülje a beágyazott hátterek összeolvadását, alkalmazza a ConstraintLayout-ot a beágyazott LinearLayout helyett. Android Studio → Profile GPU Rendering → Overdraw megmutatja az overdraw színtérképét (kék = 1x, piros = 3x+).

Szükséges a super.onDraw() egy egyedi View-ban?

Igen, ha a View-nak van háttere (background). A super.onDraw() megrajzolja a View hátterét. Ha az egyedi View-nak nincs háttere, vagy saját hátteret rajzol, a super.onDraw() elhagyható — ez egy rajzolási áthaladást takarít meg. ViewGroup esetén a super.dispatchDraw() kötelező — ez rajzolja meg a gyermek View-okat.

Összefoglalás

  • View Lifecycle — három rajzolási fázis: onMeasure (méretek), onLayout (pozíció), onDraw (rajzolás).
  • Az onMeasure feldolgozza a MeasureSpec-et (EXACTLY, AT_MOST, UNSPECIFIED) és meghívja a setMeasuredDimension-t.
  • Az onLayout a ViewGroup-ban elrendezi a gyermek View-okat a left/top/right/bottom koordinátákkal.
  • Az onDraw megrajzolja a tartalmat a Canvas-ra — ne hozzon létre objektumokat ezen a metóduson belül.
  • Az invalidate() csak az onDraw-t hívja meg, a requestLayout() — a teljes ciklust: onMeasure → onLayout → onDraw.
  • Az egyedi View-ok 15–40%-kal gyorsítják az UI-t, de megkövetelik az onMeasure helyes megvalósítását és az objektumok gyorsítótárazását az onDraw-ban.
  • Összetett grafikákhoz használja a Hardware Acceleration-t és gyorsítótárazza a Path/Bitmap-et.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is