View Lifecycle — co to je, procesy onMeasure onLayout onDraw

Autor: IT Sectr Publikováno: 2026-03-05 Doba čtení: 10 min

View Lifecycle — sekvence metod, které Android volá pro vykreslení a překreslení prvku uživatelského rozhraní (View) na obrazovce. Na rozdíl od Activity nebo Fragment je View lehkou komponentou, která nemá rozšířený životní cyklus, ale prochází přísným třífázovým procesem: onMeasure (měření), onLayout (umístění), onDraw (kreslení). Porozumění View Lifecycle je nezbytné pro vytváření vlastních View, optimalizaci výkonu a řešení problémů s vykreslováním. Podle Google vlastní View zrychlují UI o 15–40% ve srovnání s kombinací standardních vnořených ViewGroup při správné implementaci. Dokumentace Androidu k vlastním View popisuje onMeasure, onLayout a onDraw jako tři pilíře View Lifecycle.

Hlavní body

  • View Lifecycle se skládá ze tří fází: onMeasure (rozměry), onLayout (pozice), onDraw (kreslení) — a spouští se při invalidate() nebo requestLayout().
  • onMeasure vypočítá šířku a výšku View na základě MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED).
  • onLayout rozmístí podřízené View uvnitř ViewGroup a určí jejich souřadnice left, top, right, bottom.
  • onDraw vykreslí obsah View na Canvas: pozadí, text, tvary, obrázky.
  • Nesprávný View Lifecycle — hlavní příčina problémů s výkonem UI (jank, dropped frames) a hierarchií.

View Lifecycle — co to je v Androidu

View Lifecycle — proces, kterým Android View (a ViewGroup) prochází, aby se zobrazilo na obrazovce. Na rozdíl od Activity nebo Fragment View nemá onStart/onStop/onDestroy — jeho „život” se skládá z cyklického procesu měření, umístění a kreslení. Tento cyklus se spouští pokaždé, když je třeba View zobrazit nebo překreslit.

Tři fáze View Lifecycle:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — určuje požadované rozměry View. Systém předává MeasureSpec — instrukci o povolených rozměrech (přesná hodnota, maximum nebo bez omezení).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — rozmístí View a jeho potomky na obrazovce. Pro View určuje vlastní hranice, pro ViewGroup — pozice podřízených prvků.
  • onDraw(Canvas canvas) — kreslí obsah View na předaný Canvas. Systém poskytuje Canvas, který překládá příkazy do bitmapy nebo GPU.

Plný cyklus View Lifecycle také zahrnuje metody související s připojením View k oknu: onAttachedToWindow (View je připojeno k oknu, má HW acceleration) a onDetachedFromWindow (View je odpojeno, zdroje se uvolňují). Tyto metody jsou volány jednou za život View a jsou důležité pro registraci/zrušení animací, senzorů.

Podle Android Performance Blogu 65% problémů s výkonem UI (jank, přeskakování snímků) souvisí s nesprávnou implementací onMeasure a onDraw: nadměrné přepisování, volání requestLayout() bez potřeby, vytváření objektů v onDraw.

onMeasure: měření rozměrů View

onMeasure — nejdůležitější a nejkomplexnější fáze View Lifecycle. V této fázi Android určuje, kolik místa View na obrazovce zabere. Systém předává MeasureSpec — instrukce zabalené do int sestávající z režimu a velikosti.

Tři režimy MeasureSpec:

RežimKonstantaVýznamPříklad
EXACTLYMeasureSpec.EXACTLYPřesná velikost určená rodičem (match_parent nebo pevná šířka)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView může mít velikost až do zadaného maxima (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDBez omezení — View může mít libovolnou velikost (ScrollView, RecyclerView)šířka neomezená → MeasureSpec(0, UNSPECIFIED)

Implementace onMeasure by měla:

  • Zavolat setMeasuredDimension(int width, int height) pro uložení naměřených rozměrů.
  • Zohlednit padding — odečíst getPaddingLeft() + getPaddingRight() od dostupné šířky.
  • Pro ViewGroup — změřit všechny potomky pomocí measureChild() nebo measureChildWithMargins().
  • Pro wrap_content — vypočítat velikost podle obsahu (text, obrázek).
  • Ne volat requestLayout() uvnitř onMeasure — to by způsobilo nekonečnou smyčku.

Typická chyba: nezohlednění MeasureSpec při wrap_content. Pokud je View nastaveno na wrap_content, ale onMeasure nezpracovává AT_MOST a vrací pevnou velikost, View bude buď oříznuto, nebo zabere více místa, než je potřeba.

onLayout: umístění View na obrazovce

onLayout — fáze, ve které View nebo ViewGroup rozmísťuje své potomky v rámci svých hranic. Pro běžné View (ne ViewGroup) není onLayout vyžadován — systém sám volá layout() s parametry předanými od rodiče. Pro ViewGroup je onLayout povinný — bez něj nebudou podřízené View umístěny.

Signatura onLayout:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // rozmístění podřízených View
}

Parametr changed udává, zda se pozice nebo velikost View změnila oproti předchozímu layout. Pokud false — View může přeskočit přepočet pozic potomků pro optimalizaci.

Pro ViewGroup by onLayout měl:

  • Projít všechny potomky pomocí getChildCount() a getChildAt(i).
  • Pro každého potomka určit left, top, right, bottom — souřadnice uvnitř ViewGroup (s ohledem na padding).
  • Zavolat child.layout(l, t, r, b) pro každého potomka.
  • Zohlednit gravity, okraje, zarovnání.

onLayout je volán po onMeasure — naměřené rozměry jsou dostupné přes getMeasuredWidth()/getMeasuredHeight(). Pokud má podřízené View po layout() jiné skutečné rozměry, zavolá se requestLayout() pro přeměření. Tomu se říká „layout pass” a může vyvolat řetězovou reakci přepočtů.

onDraw: kreslení obsahu Canvas

onDraw — fáze, ve které View kreslí sebe na Canvas. Je to jediná fáze, která může být volána vícekrát bez onMeasure a onLayout — pokud je View označeno jako invalidate(). Canvas poskytuje API pro kreslení: drawLine, drawRect, drawCircle, drawText, drawBitmap a drawPath.

Pravidla onDraw:

  • Nevytvářejte objekty v onDraw — každé volání onDraw by mělo používat předem vytvořené objekty (Path, Paint, Rect). Vytváření objektů v onDraw způsobuje GC pauzy a přeskakování snímků.
  • Nevolajte requestLayout() nebo invalidate() uvnitř onDraw — to spustí nekonečnou smyčku překreslování.
  • Neprovádějte dlouhé výpočty — onDraw se provádí na UI vlákně. Složité výpočty by měly být přesunuty na vlákno na pozadí nebo předem vypočítány.
  • Používejte Hardware Acceleration — od API 14+ může Canvas pracovat přes GPU. Pro složitou grafiku (gradienty, stíny, rotace) poskytuje HW akcelerace zvýšení výkonu až o 300%.
  • Kreslete pouze viditelnou oblast — použijte canvas.clipRect() pro oříznutí neviditelných částí.

Pořadí kreslení v ViewGroup: pozadí (setBackgroundDrawable) → onDraw (obsah) → dispatchDraw (podřízené View) → onDrawForeground (popředí). dispatchDraw volá onDraw každého potomka. Přepsání dispatchDraw se používá pro aplikaci efektů přes podřízené prvky.

Podle statistik Android Vitals nejčastější příčiny přeskakování snímků v onDraw — vytváření objektů uvnitř metody (48%), volání decodeResource (22%) a složité operace s Path bez cache (15%).

Invalidation: kdy se View překresluje

Invalidation — mechanismus, který spouští překreslení View. Volání invalidate() označí View jako „špinavé” a naplánuje volání onDraw v dalším cyklu kreslení. Volání requestLayout() — těžší operace, která spouští plný cyklus: onMeasure → onLayout → onDraw.

MetodaCo děláKdy použít
invalidate()Volá onDraw bez onMeasure/onLayoutZměnil se pouze vzhled (barva, text, průběh)
invalidate(Rect)Překresluje pouze zadanou oblastZměnila se část View — animace, výběr
postInvalidate()Volá invalidate z ne-UI vláknaVlákno na pozadí aktualizovalo data pro kreslení
requestLayout()Spouští onMeasure → onLayout → onDrawZměnila se velikost obsahu (text, obrázek)
forceLayout()Označí View pro vynucené přeměřeníZměnil se vnitřní stav, velikost se mohla změnit

Animace a View Lifecycle: ViewPropertyAnimator a ValueAnimator volají invalidate() na každém snímku animace. ObjectAnimator volá setter na View, který pokud setter mění velikost (width/height), automaticky volá requestLayout(). To může být nákladné pro složité ViewGroup: každý requestLayout spouští celou hierarchii až k root view.

Pravidlo optimalizace: invalidate() místo requestLayout() všude, kde se mění pouze vzhled (barva, průhlednost, rotace bez změny velikosti). requestLayout používejte pouze při změně rozměrů nebo obsahu ovlivňujícího velikost.

Optimalizace vlastních View: osvědčené postupy

Vlastní View — mocný nástroj pro vytváření jedinečného UI, ale vyžadují přísné dodržování pravidel výkonu. Zde jsou klíčová doporučení Google pro optimalizaci View Lifecycle.

  • Předem vypočítejte vše, co lze vypočítat — rozměry, souřadnice, cestu Path, barvy gradientu. V onDraw používejte pouze kreslení.
  • Kešujte výsledek měření — pokud má View pevné rozměry, uložte MeasureSpec a vracejte setMeasuredDimension bez dalších výpočtů.
  • Používejte ViewConfiguration — getScaledTouchSlop, getScaledMinimumFlingVelocity — pro zpracování dotyků.
  • Minimalizujte počet View v hierarchii — vlastní View kombinující více prvků jsou vždy rychlejší než ViewGroup se 3–5 vnořenými View. Google doporučuje ne více než 10 vnořených View na jedné obrazovce.
  • Používejte ConstraintLayout pro plochou hierarchii — vytváří jedinou ViewGroup s výkonem blízkým RelativeLayout, ale bez vnořování.
  • Vypněte hardwarovou vrstvu při překreslování — použijte setLayerType(LAYER_TYPE_HARDWARE) pro View s animacemi a setLayerType(LAYER_TYPE_NONE) po dokončení.
  • Používejte invalidate() s Rect — překreslujte pouze změněnou oblast, ne celé View.
  • Vyhněte se overdraw — použijte Profile GPU Rendering v Android Studio pro detekci zbytečných kreslení. Průměrný overdraw pro Google aplikace — 1.5x, maximální doporučený — 2.5x.

Příklady kódu View v Kotlinu

Příklad 1: Vlastní View — indikátor průběhu

Jednoduchý kruhový indikátor průběhu se správnou implementací onMeasure, onDraw a 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
    }
}

Kruhový indikátor průběhu: onMeasure vrací čtvercovou velikost na základě MeasureSpec, onSizeChanged si pamatuje rozměry, onDraw kreslí pozadí a oblouk průběhu. Invalidate je volán při změně průběhu — onMeasure/onLayout nejsou ovlivněny. Paint je vytvořen jednou v konstruktoru, ne v onDraw.

Příklad 2: ViewGroup — jednoduchý FlowLayout

Vlastní ViewGroup, která rozmísťuje podřízené View do řádků (jako 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 přepisuje onMeasure: měří každého potomka, přesouvá na nový řádek při překročení šířky, vypočítá celkovou výšku. onLayout rozmísťuje děti podle souřadnic s ohledem na zalomení řádků. generateLayoutParams vrací MarginLayoutParams pro podporu okrajů u podřízených View.

Příklad 3: onDraw s kešováním Path

Vlastní View kreslí hladkou Bézierovu křivku, předem vypočítává Path a kešuje jej.

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

Kešování Path: isPathDirty = true pouze při změně rozměrů View nebo volání refreshWave(). V onDraw je Path přepočítán pouze pokud je „špinavý”. To zabraňuje přepočtu Bézierovy křivky na každém snímku animace, šetří CPU.

Často kladené otázky

Čím se View Lifecycle liší od Activity Lifecycle?

View Lifecycle — cyklický proces kreslení (onMeasure → onLayout → onDraw), nezávislý na vytváření/ničení Activity. View nemá onStart/onStop — je buď viditelné (připojené k oknu), nebo není. Activity Lifecycle spravuje stav komponenty aplikace, View Lifecycle — kreslení UI.

Jaký vliv má requestLayout() na výkon?

requestLayout() spouští plný cyklus onMeasure → onLayout → onDraw pro celý strom View od kořene. Pokud je requestLayout() volán často (např. každý snímek animace), způsobuje jank a přeskakování snímků. Podle Google trvá jeden requestLayout v průměru 2–5 ms na ViewGroup s 10 prvky. Pro animace používejte invalidate().

Kdy je voláno onAttachedToWindow?

onAttachedToWindow je voláno, když je View připojeno k oknu (Window) — stává se součástí viditelné hierarchie. V tomto okamžiku View získává HW akceleraci a přístup ke zdrojům okna (WindowManager, Display). onAttachedToWindow je správné místo pro registraci posluchačů animací a BroadcastReceiver, který žije, dokud je View viditelné.

Co je overdraw a jak ho snížit?

Overdraw — situace, kdy je pixel kreslen několikrát v jednom snímku. Každý průchod navíc je ztráta GPU času. Metody snížení: nastavte windowBackground v theme (nekreslete pozadí v layout), použijte canvas.clipRect(), vyhněte se slučování vnořených pozadí, aplikujte ConstraintLayout místo vnořených LinearLayout. Android Studio → Profile GPU Rendering → Overdraw zobrazuje barevnou mapu overdraw (modrá = 1x, červená = 3x+).

Je super.onDraw() potřeba ve vlastním View?

Ano, pokud má View pozadí (background). super.onDraw() kreslí pozadí View. Pokud vaše vlastní View nemá pozadí nebo kreslíte vlastní pozadí, super.onDraw() lze vynechat — ušetří to jeden průchod kreslení. Pro ViewGroup je super.dispatchDraw() povinný — kreslí podřízené View.

Shrnutí

  • View Lifecycle — tři fáze kreslení: onMeasure (rozměry), onLayout (pozice), onDraw (kreslení).
  • onMeasure zpracovává MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) a volá setMeasuredDimension.
  • onLayout v ViewGroup rozmísťuje podřízené View se souřadnicemi left/top/right/bottom.
  • onDraw kreslí obsah na Canvas — nevytvářejte objekty uvnitř této metody.
  • invalidate() volá pouze onDraw, requestLayout() — plný cyklus onMeasure → onLayout → onDraw.
  • Vlastní View zrychlují UI o 15–40%, ale vyžadují správnou implementaci onMeasure a kešování objektů v onDraw.
  • Pro složitou grafiku používejte Hardware Acceleration a kešujte Path/Bitmap.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také