View Lifecycle — bu nədir, onMeasure onLayout onDraw prosesləri

Müəllif: IT Sectr Dərc olunub: 2026-03-05 Oxuma vaxtı: 10 dəq

View Lifecycle — Android-in istifadəçi interfeysi elementini (View) ekranda göstərmək və yenidən göstərmək üçün çağırdığı metodlar ardıcıllığıdır. Activity və ya Fragment-dən fərqli olaraq, View genişləndirilmiş həyat dövrü olmayan, lakin ciddi üç fazalı prosesdən keçən yüngül bir komponentdir: onMeasure (ölçmə), onLayout (yerləşdirmə), onDraw (çəkmə). View Lifecycle-ni başa düşmək xüsusi View-lər yaratmaq, performansı optimallaşdırmaq və göstərmə problemlərini həll etmək üçün vacibdir. Google məlumatlarına görə, düzgün tətbiq edildikdə xüsusi View-lər UI-ni 15–40% sürətləndirir standart iç-içə ViewGroup kombinasiyası ilə müqayisədə. Android-in xüsusi View-lər haqqında sənədləri onMeasure, onLayout və onDraw-ı View Lifecycle-nin üç sütunu kimi təsvir edir.

Əsas məqamlar

  • View Lifecycle üç fazadan ibarətdir: onMeasure (ölçülər), onLayout (mövqelər), onDraw (çəkmə) — və invalidate() və ya requestLayout() çağırıldıqda işə düşür.
  • onMeasure View-in enini və hündürlüyünü MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED) əsasında hesablayır.
  • onLayout ViewGroup daxilində uşaq View-ləri yerləşdirir, onların left, top, right, bottom koordinatlarını təyin edir.
  • onDraw View-in məzmununu Canvas-da çəkir: fon, mətn, fiqurlar, şəkillər.
  • Səhv View Lifecycle — UI performans problemlərinin (jank, dropped frames) və iyerarxiyanın əsas səbəbidir.

View Lifecycle — Android-də bu nədir

View Lifecycle — Android View-in (və ViewGroup-un) özünü ekranda göstərmək üçün keçdiyi prosesdir. Activity və ya Fragment-dən fərqli olaraq, View-də onStart/onStop/onDestroy yoxdur — onun "həyatı" ölçmə, yerləşdirmə və çəkmə dövri prosesindən ibarətdir. Bu dövrü View hər dəfə göstərilməli və ya yenidən çəkilməli olduqda işə düşür.

View Lifecycle-nin üç fazası:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — View-in istənilən ölçülərini təyin edir. Sistem MeasureSpec — icazə verilən ölçülər haqqında təlimat ötürür (dəqiq dəyər, maksimum və ya məhdudiyyətsiz).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — View və onun törəmələrini ekranda yerləşdirir. View üçün öz sərhədlərini, ViewGroup üçün uşaq elementlərin mövqelərini təyin edir.
  • onDraw(Canvas canvas) — View-in məzmununu verilmiş Canvas-da çəkir. Sistem əmrləri bitmap və ya GPU-ya çevirən Canvas təqdim edir.

View Lifecycle-nin tam dövrü həmçinin View-in pəncərəyə bağlanması ilə əlaqəli metodları əhatə edir: onAttachedToWindow (View pəncərəyə bağlanıb, HW acceleration var) və onDetachedFromWindow (View ayrılıb, resurslar sərbəst buraxılır). Bu metodlar View-in ömrü boyu bir dəfə çağrılır və animasiyaların, sensorların qeydiyyatı/ləğvi üçün vacibdir.

Android Performance Blog məlumatlarına görə, UI performans problemlərinin (jank, kadr buraxma) 65%-i onMeasure və onDraw-ın səhv tətbiqi ilə bağlıdır: həddindən artıq override, lazımsız requestLayout() çağırışı, onDraw-da obyekt yaratma.

onMeasure: View ölçülərinin müəyyən edilməsi

onMeasure — View Lifecycle-nin ən vacib və ən mürəkkəb fazası. Bu mərhələdə Android View-in ekranda nə qədər yer tutacağını müəyyən edir. Sistem rejim və ölçüdən ibarət int-ə yığılmış təlimatlar olan MeasureSpec ötürür.

MeasureSpec-in üç rejimi:

RejimSabitMənaNümunə
EXACTLYMeasureSpec.EXACTLYValideyn tərəfindən təyin edilmiş dəqiq ölçü (match_parent və ya sabit genişlik)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView göstərilən maksimuma qədər ölçüdə ola bilər (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDMəhdudiyyətsiz — View istənilən ölçüdə ola bilər (ScrollView, RecyclerView)genişlik məhdudiyyətsiz → MeasureSpec(0, UNSPECIFIED)

onMeasure tətbiqi aşağıdakıları etməlidir:

  • Ölçülmüş ölçüləri saxlamaq üçün setMeasuredDimension(int width, int height) çağırın.
  • Padding-i nəzərə alın — mövcud genişlikdən getPaddingLeft() + getPaddingRight() çıxın.
  • ViewGroup üçün — bütün törəmələri measureChild() və ya measureChildWithMargins() vasitəsilə ölçün.
  • wrap_content üçün — məzmun (mətn, şəkil) əsasında ölçü hesablayın.
  • onMeasure daxilində requestLayout() çağırmayın — bu sonsuz dövrü yaradacaq.

Tipik səhv: wrap_content zamanı MeasureSpec-i nəzərə almamaq. View wrap_content-ə təyin edilibsə, lakin onMeasure AT_MOST-u idarə etmir və sabit ölçü qaytarırsa, View ya kəsiləcək, ya da lazım olduğundan çox yer tutacaq.

onLayout: View-in ekranda yerləşdirilməsi

onLayout — View və ya ViewGroup-un törəmələrini öz sərhədləri daxilində yerləşdirdiyi faza. Adi View (ViewGroup olmayan) üçün onLayout tələb olunmur — sistem valideyndən ötürülən parametrlərlə layout() çağırır. ViewGroup üçün onLayout məcburidir — onsuz uşaq View-lər yerləşdirilməyəcək.

onLayout imzası:

java
@Override
protected void onLayout(boolean changed,
        int left, int top,
        int right, int bottom) {
    // uşaq View-lərin yerləşdirilməsi
}

changed parametri View-in mövqeyinin və ya ölçüsünün əvvəlki layout ilə müqayisədə dəyişib-dəyişmədiyini göstərir. false olarsa, View optimallaşdırma üçün törəmələrin mövqelərinin yenidən hesablanmasını keçə bilər.

ViewGroup üçün onLayout aşağıdakıları etməlidir:

  • Bütün törəmələri getChildCount()getChildAt(i) vasitəsilə nəzərdən keçirin.
  • Hər törəmə üçün left, top, right, bottom — ViewGroup daxilində koordinatları (padding nəzərə alınmaqla) təyin edin.
  • Hər törəmə üçün child.layout(l, t, r, b) çağırın.
  • Gravity, margins, alignment nəzərə alın.

onLayout onMeasure-dən sonra çağırılır — ölçülmüş ölçülər getMeasuredWidth()/getMeasuredHeight() vasitəsilə əlçatandır. Əgər uşaq View-in layout()-dan sonra fərqli real ölçüləri varsa, yenidən ölçmək üçün requestLayout() çağırılacaq. Buna "layout pass" deyilir və yenidən hesablamaların zəncirvari reaksiyasına səbəb ola bilər.

onDraw: Canvas məzmununun çəkilməsi

onDraw — View-in Canvas-da özünü çəkdiyi faza. Bu, onMeasure və onLayout olmadan dəfələrlə çağırıla bilən yeganə fazadır — View invalidate() kimi işarələnmişsə. Canvas çəkmək üçün API təqdim edir: drawLine, drawRect, drawCircle, drawText, drawBitmap və drawPath.

onDraw qaydaları:

  • onDraw-da obyekt yaratmayın — hər onDraw çağırışı əvvəlcədən yaradılmış obyektlərdən (Path, Paint, Rect) istifadə etməlidir. onDraw-da obyekt yaratmaq GC fasilələrinə və kadr buraxmalarına səbəb olur.
  • onDraw daxilində requestLayout() və ya invalidate() çağırmayın — bu sonsuz yenidənçəkmə dövrünü işə salacaq.
  • Uzun hesablamalar aparmayın — onDraw UI thread-də icra olunur. Mürəkkəb hesablamalar background thread-ə çıxarılmalı və ya əvvəlcədən hesablanmalıdır.
  • Hardware Acceleration istifadə edin — API 14+-dan Canvas GPU vasitəsilə işləyə bilər. Mürəkkəb qrafika (gradientlər, kölgələr, döndərmələr) üçün HW sürətləndirmə performansı 300%-ə qədər artırır.
  • Yalnız görünən sahəni çəkin — görünməyən hissələri kəsmək üçün canvas.clipRect() istifadə edin.

ViewGroup-da çəkmə ardıcıllığı: fon (setBackgroundDrawable) → onDraw (məzmun) → dispatchDraw (uşaq View-lər) → onDrawForeground (ön plan). dispatchDraw hər törəmənin onDraw-ını çağırır. dispatchDraw-ı override etmək uşaq elementlərin üzərinə effektlər əlavə etmək üçün istifadə olunur.

Android Vitals statistikasına görə, onDraw-da kadr buraxmalarının ən çox yayılmış səbəbləri metod daxilində obyekt yaratma (48%), decodeResource çağırışı (22%) və keşləmədən Path ilə mürəkkəb əməliyyatlar (15%).

Invalidation: View nə vaxt yenidən çəkilir

Invalidation — View-in yenidən çəkilməsini işə salan mexanizmdir. invalidate() çağırışı View-i "çirkli" kimi qeyd edir və növbəti çəkmə dövründə onDraw çağırışını planlaşdırır. requestLayout() çağırışı — daha "ağır" əməliyyatdır, tam dövrü işə salır: onMeasure → onLayout → onDraw.

MetodNə edirNə vaxt istifadə etməli
invalidate()onMeasure/onLayout olmadan onDraw çağırırYalnız görünüş dəyişib (rəng, mətn, gedişat)
invalidate(Rect)Yalnız göstərilən sahəni yenidən çəkirView-in bir hissəsi dəyişib — animasiya, seçim
postInvalidate()UI olmayan thread-dən invalidate çağırırFon thread-i çəkmə üçün məlumatları yeniləyib
requestLayout()onMeasure → onLayout → onDraw işə salırMəzmun ölçüsü dəyişib (mətn, şəkil)
forceLayout()View-i məcburi yenidən ölçmə üçün qeyd edirDaxili vəziyyət dəyişib, ölçü dəyişə bilər

Animasiyalar və View Lifecycle: ViewPropertyAnimator və ValueAnimator animasiyanın hər kadrında invalidate() çağırır. ObjectAnimator View-də setter çağırır, əgər setter ölçünü (width/height) dəyişirsə, avtomatik requestLayout() çağırır. Bu, mürəkkəb ViewGroup üçün bahalı ola bilər: hər requestLayout kök view-ə qədər tam iyerarxiyanı işə salır.

Optimallaşdırma qaydası: invalidate() requestLayout() əvəzinə yalnız görünüş dəyişdikdə (rəng, şəffaflıq, ölçü dəyişmədən döndərmə). requestLayout yalnız ölçülər və ya ölçüyə təsir edən məzmun dəyişdikdə istifadə edin.

Xüsusi View optimallaşdırması: ən yaxşı təcrübələr

Xüsusi View-lər unikal UI yaratmaq üçün güclü alətdir, lakin onlar performans qaydalarına ciddi riayət etməyi tələb edir. View Lifecycle optimallaşdırması üçün Google-ın əsas tövsiyələri.

  • Hesablana bilən hər şeyi əvvəlcədən hesablayın — ölçülər, koordinatlar, Path yolu, gradient rəngləri. onDraw-da yalnız çəkmədən istifadə edin.
  • Ölçmə nəticəsini keşləyin — View-in sabit ölçüləri varsa, MeasureSpec-i saxlayın və əlavə hesablamalar olmadan setMeasuredDimension qaytarın.
  • ViewConfiguration istifadə edin — getScaledTouchSlop, getScaledMinimumFlingVelocity — toxunuşları idarə etmək üçün.
  • İyerarxiyada View sayını minimuma endirin — bir neçə elementi birləşdirən xüsusi View-lər həmişə 3–5 iç-içə View-dən ibarət ViewGroup-dan daha sürətlidir. Google bir ekranda 10-dan çox iç-içə View tövsiyə etmir.
  • Yastı iyerarxiya üçün ConstraintLayout istifadə edin — tək ViewGroup qurur, performansı RelativeLayout-a yaxındır, lakin iç-içəlik yoxdur.
  • Yenidən çəkmə zamanı hardware layer-i söndürün — animasiyalı View-lər üçün setLayerType(LAYER_TYPE_HARDWARE) və bitdikdən sonra setLayerType(LAYER_TYPE_NONE) istifadə edin.
  • Rect ilə invalidate() istifadə edin — bütün View-i deyil, yalnız dəyişmiş sahəni yenidən çəkin.
  • Overdraw-dan qaçının — lazımsız çəkmələri aşkar etmək üçün Android Studio-da Profile GPU Rendering istifadə edin. Google tətbiqləri üçün orta overdraw — 1.5x, maksimum tövsiyə olunan — 2.5x.

Kotlin-də View kod nümunələri

Nümunə 1: Xüsusi View — gedişat göstəricisi

onMeasure, onDraw və invalidate-in düzgün tətbiqi ilə sadə dairəvi gedişat göstəricisi.

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

Dairəvi gedişat çubuğu: onMeasure MeasureSpec əsasında kvadrat ölçü qaytarır, onSizeChanged ölçüləri yadda saxlayır, onDraw fon və gedişat qövsünü çəkir. Invalidate gedişat dəyişdikdə çağırılır — onMeasure/onLayout təsirlənmir. Paint konstruktorda bir dəfə yaradılır, onDraw-da yox.

Nümunə 2: ViewGroup — sadə FlowLayout

Uşaq View-ləri sətirlərə yerləşdirən xüsusi ViewGroup (Flexbox wrap kimi).

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-i override edir: hər törəməni ölçür, genişlik aşıldıqda yeni sətirə keçir, ümumi hündürlüyü hesablayır. onLayout uşaqları sətir keçidləri nəzərə alınmaqla koordinatlara görə yerləşdirir. generateLayoutParams uşaq View-lərdə margin dəstəyi üçün MarginLayoutParams qaytarır.

Nümunə 3: Path keşləmə ilə onDraw

Xüsusi View Path-i əvvəlcədən hesablayaraq və keşləyərək hamar Bezier əyrisi çəkir.

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 keşləmə: isPathDirty = true yalnız View ölçüləri dəyişdikdə və ya refreshWave() çağırıldıqda. onDraw-da Path yalnız "çirkli" olduqda yenidən hesablanır. Bu, CPU-ya qənaət edərək Bezier əyrisinin hər animasiya kadrında yenidən hesablanmasının qarşısını alır.

Tez-tez verilən suallar

View Lifecycle Activity Lifecycle-dən nə ilə fərqlənir?

View Lifecycle — Activity-nin yaradılması/məhv edilməsindən asılı olmayan dövri çəkmə prosesidir (onMeasure → onLayout → onDraw). View-də onStart/onStop yoxdur — o ya görünür (pəncərəyə bağlıdır), ya da yox. Activity Lifecycle tətbiq komponentinin vəziyyətini idarə edir, View Lifecycle — UI-nin çəkilməsini.

requestLayout() performansa necə təsir edir?

requestLayout() kökdən bütün View ağacı üçün tam dövrü işə salır: onMeasure → onLayout → onDraw. requestLayout() tez-tez çağırılarsa (məsələn, animasiyanın hər kadrında), bu jank və kadr buraxmalarına səbəb olur. Google məlumatlarına görə, bir requestLayout 10 elementli ViewGroup-da orta hesabla 2–5 ms çəkir. Animasiyalar üçün invalidate() istifadə edin.

onAttachedToWindow nə vaxt çağırılır?

onAttachedToWindow View pəncərəyə (Window) bağlandıqda — görünən iyerarxiyanın bir hissəsi olduqda çağırılır. Bu anda View HW acceleration və Window resurslarına (WindowManager, Display) çıxış əldə edir. onAttachedToWindow animasiya dinləyicilərini və View görünən müddətdə yaşayan BroadcastReceiver-i qeydiyyatdan keçirmək üçün düzgün yerdir.

Overdraw nədir və onu necə azaltmaq olar?

Overdraw — pikselin bir kadrda bir neçə dəfə çəkildiyi vəziyyətdir. Hər əlavə keçid GPU vaxtının itkisidir. Azaltma üsulları: theme-də windowBackground təyin edin (layout-da fon çəkməyin), canvas.clipRect() istifadə edin, iç-içə fonları birləşdirməkdən qaçının, iç-içə LinearLayout əvəzinə ConstraintLayout tətbiq edin. Android Studio → Profile GPU Rendering → Overdraw overdraw rəng xəritəsini göstərir (mavi = 1x, qırmızı = 3x+).

Xüsusi View-də super.onDraw() lazımdırmı?

Bəli, View-in fonu (background) varsa. super.onDraw() View-in fonunu çəkir. Əgər xüsusi View-in fonu yoxdursa və ya öz fonunuzu çəkirsinizsə, super.onDraw() çağırılmaya bilər — bu bir çəkmə keçidinə qənaət edər. ViewGroup üçün super.dispatchDraw() məcburidir — uşaq View-ləri çəkir.

Nəticə

  • View Lifecycle — üç çəkmə fazası: onMeasure (ölçülər), onLayout (mövqe), onDraw (çəkmə).
  • onMeasure MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) emal edir və setMeasuredDimension çağırır.
  • onLayout ViewGroup-da uşaq View-ləri left/top/right/bottom koordinatları ilə yerləşdirir.
  • onDraw məzmunu Canvas-da çəkir — bu metod daxilində obyekt yaratmayın.
  • invalidate() yalnız onDraw çağırır, requestLayout() — tam dövrü onMeasure → onLayout → onDraw.
  • Xüsusi View-lər UI-ni 15–40% sürətləndirir, lakin onMeasure-nin düzgün tətbiqi və onDraw-da obyekt keşləmə tələb edir.
  • Mürəkkəb qrafika üçün Hardware Acceleration istifadə edin və Path/Bitmap keşləyin.

Açar təslim mobil tətbiq hazırlayacağıq

IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.

Layihəni müzakirə et

Həm də oxuyun