onLayout(): apa itu, algoritma penempatan dan parameter metode

Penulis: IT Sectr Diterbitkan: 2026-07-22 Waktu membaca: 9 mnt

onLayout() — adalah metode kelas ViewGroup yang menentukan posisi dan ukuran View anak pada bidang koordinat kontainer induk. Sistem Android memanggil onLayout setelah fase pengukuran (onMeasure), ketika untuk setiap View anak sudah diketahui lebar dan tinggi terukur. Menurut Android Developers Documentation (2026), onLayout adalah metode wajib untuk ditimpa di setiap ViewGroup kustom, karena implementasi standar ViewGroup tidak melakukan penempatan otomatis anak.

Poin Utama

  • onLayout(boolean changed, int l, int t, int r, int b) — metode ViewGroup yang menentukan posisi elemen anak dalam koordinat induk
  • child.layout(l, t, r, b) — panggilan untuk setiap View anak yang menetapkan batas akhirnya
  • Fase layout mengikuti setelah fase measure dan sebelum fase draw dalam siklus hidup View
  • getWidth() dan getHeight() tersedia hanya setelah eksekusi onLayout, berbeda dengan getMeasuredWidth setelah onMeasure
  • requestLayout() — metode yang memulai pemanggilan ulang onMeasure dan onLayout saat perubahan data yang mempengaruhi penempatan

Apa itu onLayout()?

onLayout(boolean changed, int l, int t, int r, int b) — adalah metode protected dari kelas ViewGroup yang dipanggil oleh sistem untuk menempatkan View anak di dalam kontainer induk. Pengembang menimpa metode ini ketika membuat ViewGroup kustom dengan tata letak elemen yang tidak standar: bertingkat, grid, pola kotak-kotak, atau menurut koordinat sembarang. Setiap View anak menerima batas akhirnya melalui panggilan child.layout().

Parameter changed menunjukkan apakah posisi atau ukuran ViewGroup itu sendiri telah berubah sejak layout terakhir. Jika changed bernilai true, semua elemen anak kemungkinan juga perlu diposisikan ulang. Parameter l, t, r, b adalah koordinat sudut kiri atas dan kanan bawah ViewGroup dalam sistem koordinat induknya. Di dalam onLayout, pengembang menggunakan nilai-nilai ini sebagai koordinat awal untuk menempatkan anak.

ViewGroup — adalah satu-satunya kelas yang menimpa onLayout. View biasa (bukan ViewGroup) tidak memiliki elemen anak dan tidak memerlukan onLayout — penempatannya sendiri ditangani oleh kontainer induk. Bahkan jika View biasa menimpa onLayout, sistem tidak akan memanggilnya. Ini adalah perbedaan mendasar antara onLayout dan onMeasure, yang dipanggil untuk setiap View.

Alur eksekusi fase layout

Fase layout dimulai dengan pemanggilan metode publik layout(int l, int t, int r, int b) pada View akar. Metode ini menetapkan koordinat akhir View itu sendiri dan memanggil onLayout jika View adalah ViewGroup. Kemudian onLayout secara rekursif memanggil child.layout() untuk setiap elemen anak, dan proses berulang ke bawah hierarki. Dengan demikian, layout menyebar dari akar ke daun.

Sebelum pemanggilan onLayout, sistem memeriksa apakah ukuran View telah berubah dibandingkan siklus sebelumnya. Jika ukuran tidak berubah dan requestLayout tidak dipanggil, onLayout mungkin tidak dipanggil — sistem menggunakan hasil layout sebelumnya. Ini adalah optimasi yang mencegah perhitungan ulang posisi yang tidak perlu selama animasi atau scroll, ketika hanya konten yang berubah, bukan ukuran.

requestLayout() — adalah metode View yang memberi tahu sistem bahwa layout View sudah usang dan perlu dihitung ulang. Pemanggilan requestLayout menghasilkan siklus lengkap: pertama onMeasure dipanggil, kemudian onLayout, lalu onDraw. Berbeda dengan invalidate yang hanya memulai penggambaran ulang, requestLayout memulai perhitungan ulang lengkap ukuran dan posisi. Pemanggilan berlebihan requestLayout adalah penyebab umum masalah kinerja.

Parameter onLayout: l, t, r, b

l (left) — koordinat X tepi kiri ViewGroup dalam sistem koordinat induknya. t (top) — koordinat Y tepi atas. r (right) — koordinat X tepi kanan. b (bottom) — koordinat Y tepi bawah. Lebar ViewGroup dihitung sebagai r - l, tinggi sebagai b - t. Koordinat ini sudah mencakup semua padding ViewGroup itu sendiri.

Di dalam onLayout, pengembang memanggil child.layout(int childLeft, int childTop, int childRight, int childBottom) untuk setiap View anak. Koordinat yang diteruskan ke child.layout harus dalam sistem koordinat ViewGroup induk. Biasanya, childLeft dan childTop dihitung dengan mempertimbangkan padding induk: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.

ParameterDeskripsiPenggunaan umum
l (left)Koordinat tepi kiri ViewGroup di indukTitik awal pada sumbu X untuk elemen anak
t (top)Koordinat tepi atas ViewGroup di indukTitik awal pada sumbu Y untuk elemen anak
r (right)Koordinat tepi kanan ViewGroup di indukBatas atas lebar, r - l = getWidth()
b (bottom)Koordinat tepi bawah ViewGroup di indukBatas atas tinggi, b - t = getHeight()

Perhitungan koordinat untuk elemen anak

Koordinat anak dihitung dengan rumus: childLeft = l + paddingLeft + (marginLeft if present), childRight = childLeft + child.getMeasuredWidth(). Demikian pula untuk vertikal: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). Setelah keempat nilai ini dihitung, child.layout(childLeft, childTop, childRight, childBottom) dipanggil.

Contoh ViewGroup kustom dengan onLayout di Kotlin

Mari buat FlowLayout — ViewGroup kustom yang menempatkan View anak dalam baris, memindahkan elemen ke baris baru ketika baris saat ini penuh. Ini adalah analog Flexbox dengan wrap dalam satu bidang. onLayout mengiterasi semua View anak, menghitung posisi untuk masing-masing dan memanggil child.layout() dengan batas yang benar.

kotlin
class FlowLayout(context: Context)
    : ViewGroup(context) {

    private val horizontalSpacing = 12
    private val verticalSpacing = 8

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        val parentWidth =
            MeasureSpec.getSize(widthMeasureSpec)
        var rowX = paddingLeft
        var rowY = paddingTop
        var maxRowHeight = 0

        for (i in 0 until childCount) {
            val child = getChildAt(i)
            measureChildWithMargins(child,
                widthMeasureSpec, 0,
                heightMeasureSpec, 0)

            if (rowX + child.measuredWidth >
                parentWidth - paddingRight) {
                rowX = paddingLeft
                rowY += maxRowHeight + verticalSpacing
                maxRowHeight = 0
            }

            rowX += child.measuredWidth +
                horizontalSpacing
            maxRowHeight = maxOf(maxRowHeight,
                child.measuredHeight)
        }

        val totalHeight = rowY + maxRowHeight +
            paddingBottom
        setMeasuredDimension(
            resolveSize(parentWidth, widthMeasureSpec),
            resolveSize(totalHeight, heightMeasureSpec))
    }

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        val parentWidth = r - l
        var rowX = paddingLeft
        var rowY = paddingTop
        var maxRowHeight = 0

        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val cw = child.measuredWidth
            val ch = child.measuredHeight

            if (rowX + cw >
                parentWidth - paddingRight) {
                rowX = paddingLeft
                rowY += maxRowHeight + verticalSpacing
                maxRowHeight = 0
            }

            child.layout(rowX, rowY,
                rowX + cw, rowY + ch)
            rowX += cw + horizontalSpacing
            maxRowHeight =
                maxOf(maxRowHeight, ch)
        }
    }

    override fun generateLayoutParams(attrs: AttributeSet?)
        : LayoutParams =
        MarginLayoutParams(context, attrs)
}

Perbedaan antara onLayout dan onMeasure

onMeasure dan onLayout — adalah dua fase berurutan dalam siklus hidup View yang melakukan tugas yang secara fundamental berbeda. onMeasure menentukan ukuran yang diinginkan (measured) View, sementara onLayout menetapkan koordinat dan ukuran aktual (actual). Perbedaan utama: dalam onMeasure, ukuran bisa bersifat sementara dan nantinya dikoreksi oleh induk, sedangkan dalam onLayout, posisi akhir setiap View anak ditetapkan.

onMeasure dipanggil untuk setiap View, termasuk View daun (TextView, ImageView, Button). onLayout dipanggil hanya untuk ViewGroup. Ini dijelaskan oleh fakta bahwa penempatan adalah tanggung jawab kontainer induk, bukan View itu sendiri. View daun menerima posisinya melalui layout() yang dipanggil dari onLayout induk.

getMeasuredWidth() dan getMeasuredHeight() tersedia setelah onMeasure, sedangkan getWidth() dan getHeight() — hanya setelah onLayout. Jika Anda mengakses getWidth() di dalam onMeasure, nilai dari siklus sebelumnya atau nol akan dikembalikan. Oleh karena itu, untuk menghitung ukuran dalam onMeasure, gunakan MeasureSpec dan children secara berurutan.

Kesalahan umum saat bekerja dengan onLayout

Penempatan tanpa memperhitungkan padding — kesalahan pertama saat mengimplementasikan onLayout. Pengembang sering lupa menambahkan paddingLeft dan paddingTop induk ke koordinat awal View anak. Akibatnya, children ditampilkan di tepi ViewGroup, mengabaikan jarak yang ditetapkan melalui setPadding() atau di XML. Perhitungan yang benar: childLeft = paddingLeft + offsetX.

Pemanggilan layout untuk anak yang tidak terlihat — masalah umum kedua. Jika ViewGroup berisi View anak dengan visibility sama dengan GONE, mereka tidak perlu diposisikan — mereka tidak memakan tempat. Namun, onLayout harus menangani kasus ini dengan benar, melewatkan anak GONE. Untuk anak INVISIBLE, layout tetap harus dipanggil — mereka mempertahankan tempatnya meskipun tidak ditampilkan.

Mengabaikan parameter changed — kesalahan ketiga. Parameter changed menunjukkan apakah ukuran atau posisi ViewGroup telah berubah. Jika changed == false, koordinat yang di-cache dapat digunakan tanpa menghitung ulang layout semua elemen anak. Namun, caching layout secara penuh adalah tugas yang sulit dan sebagian besar implementasi onLayout cukup menghitung ulang semua elemen setiap kali. Ini dapat diterima untuk jumlah anak yang sedikit.

Pertanyaan Umum

Bisakah saya tidak menimpa onLayout di ViewGroup?

Bisa, jika ViewGroup menggunakan LayoutParams standar dan tidak menambahkan logika penempatan khusus. Namun, implementasi standar onLayout di ViewGroup tidak melakukan tindakan apa pun — elemen anak tidak akan diposisikan. Dalam praktiknya, semua ViewGroup (LinearLayout, RelativeLayout, FrameLayout) menimpa onLayout.

Apa perbedaan antara layout dan onLayout?

layout() — adalah metode final publik dari View, dipanggil oleh sistem atau ViewGroup induk. Ia menetapkan koordinat View itu sendiri dan memanggil onLayout jika View adalah ViewGroup. onLayout() — adalah metode protected yang ditimpa oleh pengembang untuk penempatan khusus elemen anak.

Bisakah onLayout memanggil requestLayout?

Secara teknis — ya, bisa. Tetapi ini sangat tidak disarankan karena menyebabkan rekursi tak terbatas: requestLayout → onMeasure → onLayout → requestLayout. Jika requestLayout dipanggil di dalam onLayout, sistem akan melempar pengecualian StackOverflowError. Semua perubahan ukuran harus dilakukan sebelum onLayout.

Bagaimana onLayout bekerja dengan animasi?

Animasi layout (LayoutTransition) mencegat perubahan posisi View anak dan menerapkan animasi transisi. Dengan LayoutTransition aktif, onLayout pertama menetapkan posisi akhir, kemudian LayoutTransition menganimasikan perpindahan dari posisi lama ke posisi baru. Ini memerlukan implementasi onLayout yang benar dengan koordinat akhir yang tepat.

Mengapa onLayout tidak dipanggil setelah invalidate?

invalidate() hanya memulai fase draw (penggambaran ulang), tanpa mempengaruhi measure dan layout. Untuk memicu onLayout, Anda perlu memanggil requestLayout(), yang memulai siklus lengkap: measure → layout → draw. invalidate lebih efisien untuk memperbarui tampilan ketika ukuran dan posisi tidak berubah.

Kesimpulan

  • onLayout() — metode ViewGroup yang menentukan posisi akhir View anak setelah selesainya fase pengukuran
  • child.layout(l, t, r, b) — mekanisme utama penetapan koordinat untuk setiap elemen anak
  • Parameter l, t, r, b — koordinat tepi ViewGroup dalam sistem induk, lebar = r - l, tinggi = b - t
  • Fase layout menyebar secara rekursif dari View akar ke elemen anak, memanggil onLayout pada setiap ViewGroup
  • requestLayout() memulai siklus lengkap perhitungan ulang ukuran dan posisi, berbeda dengan invalidate yang hanya memulai penggambaran ulang
  • Memperhitungkan padding dalam onLayout adalah wajib — koordinat awal anak harus mencakup paddingLeft dan paddingTop induk

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