View Lifecycle — apa itu, proses onMeasure onLayout onDraw

Penulis: IT Sectr Diterbitkan: 2026-03-05 Waktu membaca: 10 mnt

View Lifecycle — urutan metode yang dipanggil Android untuk merender dan merender ulang elemen antarmuka pengguna (View) di layar. Tidak seperti Activity atau Fragment, View adalah komponen ringan yang tidak memiliki siklus hidup yang diperluas, tetapi melalui proses tiga fase yang ketat: onMeasure (pengukuran), onLayout (penempatan), onDraw (penggambaran). Memahami View Lifecycle diperlukan untuk membuat View kustom, mengoptimalkan performa, dan mengatasi masalah rendering. Menurut Google, View kustom mempercepat UI sebesar 15–40% dibandingkan dengan kombinasi ViewGroup standar bersarang jika diimplementasikan dengan benar. Dokumentasi Android tentang View kustom menjelaskan onMeasure, onLayout, dan onDraw sebagai tiga pilar View Lifecycle.

Poin utama

  • View Lifecycle terdiri dari tiga fase: onMeasure (ukuran), onLayout (posisi), onDraw (penggambaran) — dan dipicu saat invalidate() atau requestLayout().
  • onMeasure menghitung lebar dan tinggi View berdasarkan MeasureSpec (AT_MOST, EXACTLY, UNSPECIFIED).
  • onLayout menempatkan View anak di dalam ViewGroup, menentukan koordinat left, top, right, bottom.
  • onDraw merender konten View di Canvas: latar belakang, teks, bentuk, gambar.
  • View Lifecycle yang salah — penyebab utama masalah performa UI (jank, dropped frames) dan hierarki.

View Lifecycle — apa itu di Android

View Lifecycle — proses yang dilalui Android View (dan ViewGroup) untuk menampilkan dirinya di layar. Tidak seperti Activity atau Fragment, View tidak memiliki onStart/onStop/onDestroy — “hidupnya” terdiri dari proses siklis pengukuran, penempatan, dan penggambaran. Siklus ini dipicu setiap kali View perlu ditampilkan atau digambar ulang.

Tiga fase View Lifecycle:

  • onMeasure(int widthMeasureSpec, int heightMeasureSpec) — menentukan ukuran yang diinginkan dari View. Sistem meneruskan MeasureSpec — instruksi tentang ukuran yang diizinkan (nilai tepat, maksimum, atau tanpa batasan).
  • onLayout(boolean changed, int left, int top, int right, int bottom) — menempatkan View dan keturunannya di layar. Untuk View menentukan batasnya sendiri, untuk ViewGroup — posisi elemen anak.
  • onDraw(Canvas canvas) — menggambar konten View di Canvas yang diberikan. Sistem menyediakan Canvas yang menerjemahkan perintah ke bitmap atau GPU.

Siklus lengkap View Lifecycle juga mencakup metode yang terkait dengan pelekatan View ke jendela: onAttachedToWindow (View terpasang ke jendela, memiliki akselerasi HW) dan onDetachedFromWindow (View terlepas, sumber daya dibebaskan). Metode ini dipanggil satu kali selama masa View dan penting untuk mendaftarkan/membatalkan animasi, sensor.

Menurut Android Performance Blog, 65% masalah performa UI (jank, frame terlewat) terkait dengan implementasi onMeasure dan onDraw yang salah: override berlebihan, pemanggilan requestLayout() tanpa perlu, pembuatan objek di onDraw.

onMeasure: mengukur dimensi View

onMeasure — fase terpenting dan paling kompleks dari View Lifecycle. Pada tahap ini Android menentukan berapa banyak ruang yang akan ditempati View di layar. Sistem meneruskan MeasureSpec — instruksi yang dikemas dalam int yang terdiri dari mode dan ukuran.

Tiga mode MeasureSpec:

ModeKonstantaArtiContoh
EXACTLYMeasureSpec.EXACTLYUkuran tepat yang ditentukan oleh induk (match_parent atau lebar tetap)width=400dp → MeasureSpec(400, EXACTLY)
AT_MOSTMeasureSpec.AT_MOSTView dapat berukuran hingga maksimum yang ditentukan (wrap_content)width ≤ 400dp → MeasureSpec(400, AT_MOST)
UNSPECIFIEDMeasureSpec.UNSPECIFIEDTanpa batasan — View dapat berukuran berapa pun (ScrollView, RecyclerView)lebar tidak terbatas → MeasureSpec(0, UNSPECIFIED)

Implementasi onMeasure harus:

  • Memanggil setMeasuredDimension(int width, int height) untuk menyimpan ukuran yang diukur.
  • Memperhitungkan padding — kurangi getPaddingLeft() + getPaddingRight() dari lebar yang tersedia.
  • Untuk ViewGroup — ukur semua keturunan melalui measureChild() atau measureChildWithMargins().
  • Untuk wrap_content — hitung ukuran berdasarkan konten (teks, gambar).
  • Jangan memanggil requestLayout() di dalam onMeasure — ini akan menyebabkan loop tak terbatas.

Kesalahan umum: tidak memperhitungkan MeasureSpec saat wrap_content. Jika View diatur ke wrap_content, tetapi onMeasure tidak memproses AT_MOST dan mengembalikan ukuran tetap, View akan terpotong atau mengambil lebih banyak ruang daripada yang dibutuhkan.

onLayout: menempatkan View di layar

onLayout — fase di mana View atau ViewGroup menempatkan keturunannya di dalam batasnya. Untuk View biasa (bukan ViewGroup) onLayout tidak diperlukan — sistem sendiri memanggil layout() dengan parameter dari induk. Untuk ViewGroup onLayout wajib — tanpanya View anak tidak akan ditempatkan.

Signature onLayout:

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

Parameter changed menunjukkan apakah posisi atau ukuran View berubah dibandingkan dengan layout sebelumnya. Jika false — View dapat melewatkan perhitungan ulang posisi keturunan untuk optimasi.

Untuk ViewGroup onLayout harus:

  • Menelusuri semua keturunan melalui getChildCount() dan getChildAt(i).
  • Untuk setiap keturunan menentukan left, top, right, bottom — koordinat di dalam ViewGroup (dengan padding).
  • Memanggil child.layout(l, t, r, b) untuk setiap keturunan.
  • Memperhitungkan gravity, margin, perataan.

onLayout dipanggil setelah onMeasure — ukuran yang diukur tersedia melalui getMeasuredWidth()/getMeasuredHeight(). Jika View anak memiliki ukuran nyata yang berbeda setelah layout(), requestLayout() akan dipanggil untuk pengukuran ulang. Ini disebut “layout pass” dan dapat menyebabkan reaksi berantai perhitungan ulang.

onDraw: menggambar konten Canvas

onDraw — fase di mana View menggambar dirinya di Canvas. Ini adalah satu-satunya fase yang dapat dipanggil berkali-kali tanpa onMeasure dan onLayout — jika View ditandai sebagai invalidate(). Canvas menyediakan API untuk menggambar: drawLine, drawRect, drawCircle, drawText, drawBitmap, dan drawPath.

Aturan onDraw:

  • Jangan membuat objek di onDraw — setiap panggilan onDraw harus menggunakan objek yang telah dibuat sebelumnya (Path, Paint, Rect). Membuat objek di onDraw menyebabkan jeda GC dan frame terlewat.
  • Jangan memanggil requestLayout() atau invalidate() di dalam onDraw — ini akan memicu loop penggambaran ulang tak terbatas.
  • Jangan melakukan perhitungan panjang — onDraw dijalankan di UI thread. Perhitungan kompleks harus dipindahkan ke thread latar belakang atau dihitung sebelumnya.
  • Gunakan Hardware Acceleration — sejak API 14+ Canvas dapat bekerja melalui GPU. Untuk grafis kompleks (gradien, bayangan, rotasi) akselerasi HW memberikan peningkatan performa hingga 300%.
  • Gambar hanya area yang terlihat — gunakan canvas.clipRect() untuk memotong bagian yang tidak terlihat.

Urutan penggambaran di ViewGroup: latar belakang (setBackgroundDrawable) → onDraw (konten) → dispatchDraw (View anak) → onDrawForeground (latar depan). dispatchDraw memanggil onDraw setiap keturunan. Override dispatchDraw digunakan untuk menerapkan efek di atas elemen anak.

Menurut statistik Android Vitals, penyebab paling umum frame terlewat di onDraw — pembuatan objek di dalam metode (48%), pemanggilan decodeResource (22%), dan operasi kompleks dengan Path tanpa cache (15%).

Invalidation: kapan View digambar ulang

Invalidation — mekanisme yang memicu penggambaran ulang View. Pemanggilan invalidate() menandai View sebagai “kotor” dan menjadwalkan panggilan onDraw di siklus penggambaran berikutnya. Pemanggilan requestLayout() — operasi yang lebih berat, memicu siklus lengkap: onMeasure → onLayout → onDraw.

MetodeApa yang dilakukanKapan digunakan
invalidate()Memanggil onDraw tanpa onMeasure/onLayoutHanya tampilan yang berubah (warna, teks, progres)
invalidate(Rect)Menggambar ulang hanya area yang ditentukanBagian View berubah — animasi, seleksi
postInvalidate()Memanggil invalidate dari thread non-UIThread latar belakang memperbarui data untuk digambar
requestLayout()Memicu onMeasure → onLayout → onDrawUkuran konten berubah (teks, gambar)
forceLayout()Menandai View untuk pengukuran ulang paksaStatus internal berubah, ukuran mungkin berubah

Animasi dan View Lifecycle: ViewPropertyAnimator dan ValueAnimator memanggil invalidate() pada setiap frame animasi. ObjectAnimator memanggil setter di View yang, jika setter mengubah ukuran (width/height), secara otomatis memanggil requestLayout(). Ini bisa mahal untuk ViewGroup kompleks: setiap requestLayout memicu seluruh hierarki hingga view root.

Aturan optimasi: invalidate() daripada requestLayout() di mana pun hanya tampilan yang berubah (warna, transparansi, rotasi tanpa perubahan ukuran). Gunakan requestLayout hanya saat mengubah ukuran atau konten yang memengaruhi ukuran.

Optimasi View kustom: praktik terbaik

View kustom — alat yang ampuh untuk membuat UI yang unik, tetapi memerlukan kepatuhan ketat terhadap aturan performa. Berikut rekomendasi utama Google untuk mengoptimalkan View Lifecycle.

  • Hitung sebelumnya semua yang bisa dihitung — ukuran, koordinat, jalur Path, warna gradien. Di onDraw gunakan hanya penggambaran.
  • Cache hasil pengukuran — jika View memiliki ukuran tetap, simpan MeasureSpec dan kembalikan setMeasuredDimension tanpa perhitungan tambahan.
  • Gunakan ViewConfiguration — getScaledTouchSlop, getScaledMinimumFlingVelocity — untuk menangani sentuhan.
  • Minimalkan jumlah View dalam hierarki — View kustom yang menggabungkan beberapa elemen selalu lebih cepat daripada ViewGroup dengan 3–5 View bersarang. Google merekomendasikan tidak lebih dari 10 View bersarang dalam satu layar.
  • Gunakan ConstraintLayout untuk hierarki datar — membangun ViewGroup tunggal dengan performa mendekati RelativeLayout, tetapi tanpa sarang.
  • Nonaktifkan layer hardware saat menggambar ulang — gunakan setLayerType(LAYER_TYPE_HARDWARE) untuk View dengan animasi dan setLayerType(LAYER_TYPE_NONE) setelah selesai.
  • Gunakan invalidate() dengan Rect — gambar ulang hanya area yang berubah, bukan seluruh View.
  • Hindari overdraw — gunakan Profile GPU Rendering di Android Studio untuk mendeteksi penggambaran berlebihan. Overdraw rata-rata untuk aplikasi Google — 1.5x, maksimum yang disarankan — 2.5x.

Contoh kode View di Kotlin

Contoh 1: View kustom — indikator progres

Indikator progres melingkar sederhana dengan implementasi onMeasure, onDraw, dan invalidate yang benar.

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

Bilah progres melingkar: onMeasure mengembalikan ukuran persegi berdasarkan MeasureSpec, onSizeChanged menyimpan ukuran, onDraw menggambar latar belakang dan busur progres. Invalidate dipanggil saat progres berubah — onMeasure/onLayout tidak terpengaruh. Paint dibuat sekali di konstruktor, bukan di onDraw.

Contoh 2: ViewGroup — FlowLayout sederhana

ViewGroup kustom yang menempatkan View anak dalam baris (seperti 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 mengoverride onMeasure: mengukur setiap keturunan, pindah ke baris baru saat melebihi lebar, menghitung tinggi total. onLayout menempatkan anak berdasarkan koordinat dengan memperhitungkan pemindahan baris. generateLayoutParams mengembalikan MarginLayoutParams untuk mendukung margin di View anak.

Contoh 3: onDraw dengan caching Path

View kustom menggambar kurva Bezier halus, menghitung Path terlebih dahulu dan menyimpannya dalam cache.

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

Caching Path: isPathDirty = true hanya saat ukuran View berubah atau refreshWave() dipanggil. Di onDraw Path dihitung ulang hanya jika “kotor”. Ini mencegah perhitungan ulang kurva Bezier pada setiap frame animasi, menghemat CPU.

Pertanyaan yang sering diajukan

Apa perbedaan View Lifecycle dengan Activity Lifecycle?

View Lifecycle — proses penggambaran siklis (onMeasure → onLayout → onDraw), tidak tergantung pada pembuatan/penghancuran Activity. View tidak memiliki onStart/onStop — ia terlihat (terpasang ke jendela) atau tidak. Activity Lifecycle mengelola status komponen aplikasi, View Lifecycle — penggambaran UI.

Apa dampak requestLayout() pada performa?

requestLayout() memicu siklus lengkap onMeasure → onLayout → onDraw untuk seluruh pohon View dari root. Jika requestLayout() sering dipanggil (misalnya setiap frame animasi), ini menyebabkan jank dan frame terlewat. Menurut Google, satu requestLayout memakan rata-rata 2–5 ms pada ViewGroup dengan 10 elemen. Untuk animasi gunakan invalidate().

Kapan onAttachedToWindow dipanggil?

onAttachedToWindow dipanggil ketika View terpasang ke jendela (Window) — menjadi bagian dari hierarki yang terlihat. Pada saat ini View mendapatkan akselerasi HW dan akses ke sumber daya Window (WindowManager, Display). onAttachedToWindow adalah tempat yang tepat untuk mendaftarkan listener animasi dan BroadcastReceiver yang hidup selama View terlihat.

Apa itu overdraw dan bagaimana menguranginya?

Overdraw — situasi di mana piksel digambar beberapa kali dalam satu frame. Setiap lintasan tambahan adalah pemborosan waktu GPU. Metode pengurangan: atur windowBackground di theme (jangan menggambar latar belakang di layout), gunakan canvas.clipRect(), hindari penggabungan latar belakang bersarang, terapkan ConstraintLayout sebagai ganti LinearLayout bersarang. Android Studio → Profile GPU Rendering → Overdraw menunjukkan peta warna overdraw (biru = 1x, merah = 3x+).

Apakah super.onDraw() diperlukan di View kustom?

Ya, jika View memiliki latar belakang (background). super.onDraw() menggambar latar belakang View. Jika View kustom Anda tidak memiliki latar belakang atau Anda menggambar latar belakang sendiri, super.onDraw() dapat dilewati — ini menghemat satu lintasan penggambaran. Untuk ViewGroup super.dispatchDraw() wajib — ia menggambar View anak.

Ringkasan

  • View Lifecycle — tiga fase penggambaran: onMeasure (ukuran), onLayout (posisi), onDraw (penggambaran).
  • onMeasure memproses MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) dan memanggil setMeasuredDimension.
  • onLayout di ViewGroup menempatkan View anak dengan koordinat left/top/right/bottom.
  • onDraw menggambar konten di Canvas — jangan membuat objek di dalam metode ini.
  • invalidate() hanya memanggil onDraw, requestLayout() — siklus lengkap onMeasure → onLayout → onDraw.
  • View kustom mempercepat UI sebesar 15–40%, tetapi memerlukan implementasi onMeasure yang benar dan caching objek di onDraw.
  • Untuk grafis kompleks gunakan Hardware Acceleration dan cache Path/Bitmap.

Kami akan mengembangkan aplikasi seluler turnkey

IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.

Diskusikan proyek

Baca juga