View Lifecycle — nedir, onMeasure onLayout onDraw süreçleri

Yazar: IT Sectr Yayınlanma: 2026-03-05 Okuma süresi: 10 dk

View Lifecycle — Android’in ekranda bir kullanıcı arayüzü öğesini (View) çizmek ve yeniden çizmek için çağırdığı metotlar dizisidir. Activity veya Fragment’ın aksine, View genişletilmiş bir yaşam döngüsü olmayan hafif bir bileşendir, ancak katı bir üç aşamalı süreçten geçer: onMeasure (ölçüm), onLayout (konumlandırma), onDraw (çizim). View Lifecycle’ı anlamak, özel View’lar oluşturmak, performansı optimize etmek ve çizim sorunlarını çözmek için gereklidir. Google’a göre, özel View’lar doğru şekilde uygulandığında standart iç içe ViewGroup kombinasyonlarına kıyasla UI’yi %15–40 hızlandırır. Özel View’lar hakkında Android belgeleri, onMeasure, onLayout ve onDraw’ı View Lifecycle’ın üç temel direği olarak tanımlar.

Anahtar Noktalar

  • View Lifecycle üç aşamadan oluşur: onMeasure (boyutlar), onLayout (konumlar), onDraw (çizim) — ve invalidate() veya requestLayout() tarafından tetiklenir.
  • onMeasure, MeasureSpec’e (AT_MOST, EXACTLY, UNSPECIFIED) göre View’ın genişlik ve yüksekliğini hesaplar.
  • onLayout, ViewGroup içindeki alt View’ları düzenler ve left, top, right, bottom koordinatlarını belirler.
  • onDraw, View içeriğini Canvas’ta görüntüler: arka plan, metin, şekiller, görseller.
  • Yanlış View Lifecycle, UI performans sorunlarının (jank, düşen kareler) ve hiyerarşi sorunlarının ana nedenidir.

View Lifecycle — Android’de nedir

View Lifecycle, bir Android View’ın (ve ViewGroup) kendini ekranda görüntülemek için geçirdiği süreçtir. Activity veya Fragment’ın aksine, View’ın onStart/onStop/onDestroy’si yoktur — “ömrü” ölçme, konumlandırma ve çizimden oluşan döngüsel bir süreçten oluşur. Bu döngü, bir View’ın görüntülenmesi veya yeniden çizilmesi gerektiğinde tetiklenir.

View Lifecycle’ın üç aşaması:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — View’ın istenen boyutlarını belirler. Sistem MeasureSpec’i iletir — hangi boyutlara izin verildiğine dair bir talimat (kesin değer, maksimum veya sınırsız).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — View ve alt öğelerini ekrana yerleştirir. Bir View için kendi sınırlarını tanımlar; bir ViewGroup için alt öğelerin konumlarını belirler.
  • onDraw(Canvas canvas) — View içeriğini sağlanan Canvas üzerine çizer. Sistem, komutları bitmap veya GPU’ya dönüştüren bir Canvas sağlar.

Tam View Lifecycle döngüsü ayrıca View’ı bir pencereye eklemeyle ilgili metotları da içerir: onAttachedToWindow (View bir pencereye eklendi, HW hızlandırması var) ve onDetachedFromWindow (View ayrıldı, kaynaklar serbest bırakıldı). Bu metotlar View ömrü boyunca bir kez çağrılır ve animasyonları/sensörleri kaydetmek/iptal etmek için önemlidir.

Android Performance Blog’a göre, UI performans sorunlarının (jank, düşen kareler) %65’i onMeasure ve onDraw’ın yanlış uygulanmasıyla ilgilidir: aşırı geçersiz kılma, gereksiz requestLayout() çağrısı, onDraw’da nesne oluşturma.

onMeasure: View boyutlarını ölçme

onMeasure — View Lifecycle’ın en önemli ve en karmaşık aşaması. Bu aşamada Android, View’ın ekranda ne kadar yer kaplayacağını belirler. Sistem MeasureSpec’i iletir — bir mod ve boyuttan oluşan int paketlenmiş talimatlar.

Üç MeasureSpec modu:

ModSabitAnlamıÖrnek
EXACTLYMeasureSpec.EXACTLYÜst öğe tarafından belirlenen kesin boyut (match_parent veya sabit genişlik)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView belirtilen maksimum boyuta kadar olabilir (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDKısıtlama yok — View herhangi bir boyutta olabilir (ScrollView, RecyclerView)genişlik sınırsız → MeasureSpec(0, UNSPECIFIED)

onMeasure uygulaması şunları yapmalıdır:

  • Ölçülen boyutları kaydetmek için setMeasuredDimension(int width, int height) çağırmalıdır.
  • Padding’i hesaba katmalıdır — kullanılabilir genişlikten getPaddingLeft() + getPaddingRight() çıkarmalıdır.
  • ViewGroup için — measureChild() veya measureChildWithMargins() aracılığıyla tüm alt öğeleri ölçmelidir.
  • wrap_content için — içeriğe (metin, görsel) göre boyut hesaplamalıdır.
  • onMeasure içinde requestLayout() çağırmamalıdır — bu sonsuz döngüye neden olur.

Tipik hata: wrap_content kullanırken MeasureSpec’i dikkate almamak. Bir View wrap_content olarak ayarlanmışsa, ancak onMeasure AT_MOST’u işlemez ve sabit bir boyut döndürürse, View ya kırpılır ya da gerekenden fazla yer kaplar.

onLayout: View’ları ekrana yerleştirme

onLayout — bir View veya ViewGroup’un alt öğelerini kendi sınırları içinde düzenlediği aşama. Normal bir View için (ViewGroup değil), onLayout gerekli değildir — sistem, üst öğeden iletilen parametrelerle layout() çağırır. Bir ViewGroup için onLayout zorunludur — onsuz alt View’lar yerleştirilmez.

onLayout imzası:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // alt View’ları düzenleme
}

changed parametresi, View’ın konumunun veya boyutunun önceki layout’a göre değişip değişmediğini belirtir. false ise, View optimizasyon için alt öğe konumlarını yeniden hesaplamayı atlayabilir.

ViewGroup için onLayout şunları yapmalıdır:

  • getChildCount() ve getChildAt(i) aracılığıyla tüm alt öğeleri dolaşmalıdır.
  • Her alt öğe için left, top, right, bottom belirlemelidir — ViewGroup içindeki koordinatlar (padding hesaba katılarak).
  • Her alt öğe için child.layout(l, t, r, b) çağırmalıdır.
  • Gravity, margins, alignment’ı hesaba katmalıdır.

onLayout, onMeasure’den sonra çağrılır — ölçülen boyutlar getMeasuredWidth()/getMeasuredHeight() aracılığıyla kullanılabilir. layout() sonrası bir alt View’ın gerçek boyutları farklıysa, yeniden ölçmek için requestLayout() çağrılır. Buna “layout geçişi” denir ve zincirleme bir yeniden hesaplama reaksiyonunu tetikleyebilir.

onDraw: Canvas içeriğini çizme

onDraw — bir View’ın kendini Canvas üzerine çizdiği aşama. Bu, onMeasure ve onLayout olmadan birden çok kez çağrılabilen tek aşamadır — View invalidate() olarak işaretlenmişse. Canvas, çizim API’si sağlar: drawLine, drawRect, drawCircle, drawText, drawBitmap ve drawPath.

onDraw kuralları:

  • onDraw’da nesne oluşturmayın — her onDraw çağrısı önceden oluşturulmuş nesneleri (Path, Paint, Rect) kullanmalıdır. onDraw’da nesne oluşturmak GC duraklamalarına ve düşen karelere neden olur.
  • onDraw içinde requestLayout() veya invalidate() çağırmayın — bu sonsuz bir yeniden çizim döngüsünü tetikler.
  • Uzun hesaplamalar yapmayın — onDraw UI iş parçacığında çalışır. Karmaşık hesaplamalar arka plan iş parçacığına taşınmalı veya önceden hesaplanmalıdır.
  • Donanım hızlandırmasını kullanın — API 14+ itibarıyla, Canvas GPU aracılığıyla çalışabilir. Karmaşık grafikler (gradyanlar, gölgeler, dönüşümler) için HW hızlandırması %300’e kadar performans iyileştirmesi sağlar.
  • Yalnızca görünür alanı çizin — görünmeyen kısımları kırpmak için canvas.clipRect() kullanın.

ViewGroup’ta çizim sırası: arka plan (setBackgroundDrawable) → onDraw (içerik) → dispatchDraw (alt View’lar) → onDrawForeground (ön plan). dispatchDraw her alt öğenin onDraw’ını çağırır. dispatchDraw’ı geçersiz kılmak, alt öğelerin üzerine efekt uygulamak için kullanılır.

Android Vitals istatistiklerine göre, onDraw’da kare düşmesinin en yaygın nedenleri metot içinde nesne oluşturma (%48), decodeResource çağırma (%22) ve önbellekleme olmadan karmaşık Path işlemleridir (%15).

Geçersiz kılma: Bir View ne zaman yeniden çizilir

Geçersiz kılma — View yeniden çizimini tetikleyen mekanizma. invalidate() çağrısı View’ı “kirli” olarak işaretler ve sonraki çizim döngüsünde onDraw çağrısını programlar. requestLayout() çağrısı daha “ağır” bir işlemdir ve tam döngüyü tetikler: onMeasure → onLayout → onDraw.

MetotNe yaparNe zaman kullanılır
invalidate()onMeasure/onLayout olmadan onDraw’ı tetiklerSadece görünüm değişti (renk, metin, ilerleme)
invalidate(Rect)Sadece belirtilen alanı yeniden çizerView’ın bir kısmı değişti — animasyon, seçim
postInvalidate()UI olmayan bir iş parçacığından invalidate çağırırArka plan iş parçacığı çizim için verileri güncelledi
requestLayout()onMeasure → onLayout → onDraw’ı tetiklerİçerik boyutu değişti (metin, görsel)
forceLayout()Zorunlu yeniden ölçüm için View’ı işaretlerDahili durum değişti, boyut değişmiş olabilir

Animasyonlar ve View Lifecycle: ViewPropertyAnimator ve ValueAnimator, her animasyon karesinde invalidate() çağırır. ObjectAnimator, View üzerinde bir setter çağırır ve setter boyutu (genişlik/yükseklik) değiştirirse otomatik olarak requestLayout() çağırır. Bu, karmaşık ViewGroup’lar için maliyetli olabilir: her requestLayout, kök görünüme kadar tüm hiyerarşiyi tetikler.

Optimizasyon kuralı: yalnızca görünümün değiştiği her yerde (renk, şeffaflık, boyut değişmeden dönüş) requestLayout() yerine invalidate() kullanın. requestLayout’u yalnızca boyutları veya boyutu etkileyen içeriği değiştirirken kullanın.

Özel View optimizasyonu: en iyi uygulamalar

Özel View’lar benzersiz UI oluşturmak için güçlü bir araçtır, ancak performans kurallarına sıkı sıkıya uyulmasını gerektirir. View Lifecycle optimizasyonu için Google’ın temel önerileri şunlardır.

  • Hesaplanabilen her şeyi önceden hesaplayın — boyutlar, koordinatlar, yol, gradyan renkleri. onDraw’da yalnızca çizim yapın.
  • Ölçüm sonuçlarını önbelleğe alın — View sabit boyutlara sahipse MeasureSpec’i kaydedin ve ek hesaplama olmadan setMeasuredDimension döndürün.
  • ViewConfiguration kullanın — getScaledTouchSlop, getScaledMinimumFlingVelocity — dokunma işleme için.
  • Hiyerarşideki View sayısını en aza indirin — birden çok öğeyi birleştiren özel View’lar, 3–5 iç içe View’a sahip ViewGroup’dan her zaman daha hızlıdır. Google, ekran başına en fazla 10 iç içe View önerir.
  • Düz hiyerarşi için ConstraintLayout kullanın — RelativeLayout’a yakın performansa sahip tek bir ViewGroup oluşturur, ancak iç içelik olmadan.
  • Yeniden çizimden sonra donanım katmanını devre dışı bırakın — animasyonlu View’lar için setLayerType(LAYER_TYPE_HARDWARE) ve tamamlandıktan sonra setLayerType(LAYER_TYPE_NONE) kullanın.
  • Rect ile invalidate() kullanın — tüm View yerine yalnızca değişen alanı yeniden çizin.
  • Overdraw’dan kaçının — gereksiz yeniden çizimleri belirlemek için Android Studio’da Profile GPU Rendering kullanın. Google uygulamaları için ortalama overdraw 1.5x, önerilen maksimum 2.5x’tir.

Kotlin’de View kod örnekleri

Örnek 1: Özel View — ilerleme göstergesi

onMeasure, onDraw ve invalidate’in doğru uygulamasına sahip basit bir dairesel ilerleme göstergesi.

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

Dairesel ilerleme çubuğu: onMeasure, MeasureSpec’e göre kare boyut döndürür, onSizeChanged boyutları hatırlar, onDraw arka planı ve ilerleme yayını çizer. İlerleme değiştiğinde Invalidate çağrılır — onMeasure/onLayout etkilenmez. Paint, onDraw’da değil, yapıcıda bir kez oluşturulur.

Örnek 2: ViewGroup — basit FlowLayout

Alt View’ları satırlar halinde düzenleyen özel bir ViewGroup (Flexbox wrap gibi).

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, onMeasure’ı geçersiz kılar: her alt öğeyi ölçer, genişlik aşıldığında yeni satıra geçer, toplam yüksekliği hesaplar. onLayout, alt öğeleri satır sonlarını hesaba katarak koordinatlara göre yerleştirir. generateLayoutParams, alt View’larda kenar boşluğunu desteklemek için MarginLayoutParams döndürür.

Örnek 3: Path önbelleklemesi ile onDraw

Path’i önceden hesaplayıp önbelleğe alan, düzgün bir Bezier eğrisi çizen özel bir View.

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 önbelleklemesi: isPathDirty = true yalnızca View boyutları değiştiğinde veya refreshWave() çağrıldığında. onDraw’da Path yalnızca “kirli” ise yeniden hesaplanır. Bu, her animasyon karesinde Bezier eğrisinin yeniden hesaplanmasını önleyerek CPU tasarrufu sağlar.

Sıkça Sorulan Sorular

View Lifecycle, Activity Lifecycle’dan nasıl farklıdır?

View Lifecycle, Activity oluşturma/yok etmeden bağımsız döngüsel bir çizim sürecidir (onMeasure → onLayout → onDraw). View’ın onStart/onStop’u yoktur — ya görünür (bir pencereye ekli) ya da değildir. Activity Lifecycle, uygulama bileşeni durumunu yönetir, View Lifecycle ise UI çizimini yönetir.

requestLayout() performansı nasıl etkiler?

requestLayout(), kökten tüm View ağacı için tam onMeasure → onLayout → onDraw döngüsünü tetikler. requestLayout() sık sık çağrılırsa (örn. her animasyon karesi), jank ve düşen karelere neden olur. Google’a göre, 10 öğeli bir ViewGroup’ta bir requestLayout ortalama 2–5 ms sürer. Animasyonlar için invalidate() kullanın.

onAttachedToWindow ne zaman çağrılır?

onAttachedToWindow, bir View bir Window’a eklendiğinde çağrılır — görünür hiyerarşinin parçası olur. Bu anda View, HW hızlandırması ve Window kaynaklarına (WindowManager, Display) erişim kazanır. onAttachedToWindow, View görünür olduğu sürece yaşayan animasyon dinleyicilerini ve BroadcastReceiver’ları kaydetmek için doğru yerdir.

Overdraw nedir ve nasıl azaltılır?

Overdraw, bir pikselin tek bir karede birden çok kez çizildiği bir durumdur. Her ekstra geçiş GPU zamanını boşa harcar. Azaltma yöntemleri: temada windowBackground ayarlayın (layout’ta arka plan çizmeyin), canvas.clipRect() kullanın, iç içe arka planları birleştirmekten kaçının, iç içe LinearLayout yerine ConstraintLayout kullanın. Android Studio → Profile GPU Rendering → Overdraw, overdraw’ın renk haritasını gösterir (mavi = 1x, kırmızı = 3x+).

Özel bir View’da super.onDraw() gerekli mi?

Evet, View’ın arka planı varsa. super.onDraw(), View’ın arka planını çizer. Özel View’ınızın arka planı yoksa veya kendi arka planınızı çiziyorsanız, super.onDraw() atlanabilir — bu bir çizim geçişinden tasarruf sağlar. ViewGroup için super.dispatchDraw() zorunludur — alt View’ları çizer.

Özet

  • View Lifecycle — üç çizim aşaması: onMeasure (boyutlar), onLayout (konum), onDraw (görüntüleme).
  • onMeasure, MeasureSpec’i (EXACTLY, AT_MOST, UNSPECIFIED) işler ve setMeasuredDimension çağırır.
  • ViewGroup’ta onLayout, alt View’ları left/top/right/bottom koordinatlarıyla konumlandırır.
  • onDraw içeriği Canvas’ta görüntüler — bu metot içinde nesne oluşturmayın.
  • invalidate() yalnızca onDraw’ı tetikler, requestLayout() tam onMeasure → onLayout → onDraw döngüsünü tetikler.
  • Özel View’lar UI’yi %15–40 hızlandırır, ancak doğru onMeasure uygulaması ve onDraw’da nesne önbelleklemesi gerektirir.
  • Karmaşık grafikler için donanım hızlandırması kullanın ve Path/Bitmap’i önbelleğe alın.

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun