onMeasure() — adalah metode protected dari kelas android.view.View, yang dipanggil oleh sistem Android untuk menentukan dimensi View. Sistem meneruskan dua objek MeasureSpec ke metode, masing-masing berisi mode pengukuran (EXACTLY, AT_MOST, atau UNSPECIFIED) dan ukuran yang diusulkan oleh kontainer induk. Menurut Android Developers Documentation (2026), menimpa onMeasure dengan penanganan MeasureSpec yang benar adalah langkah wajib untuk semua View dan ViewGroup kustom yang memerlukan kontrol presisi atas dimensi.
Poin Penting
onMeasure(int widthMeasureSpec, int heightMeasureSpec) — adalah metode kelas View yang dipanggil oleh sistem Android untuk menentukan lebar dan tinggi tampilan. Pengembang menimpa metode ini untuk menunjukkan ukuran yang seharusnya dimiliki View berdasarkan batasan yang diteruskan dalam MeasureSpec. Tanpa penimpaan onMeasure yang benar, View kustom mungkin ditampilkan secara tidak benar atau tidak ditampilkan sama sekali.
Sistem memanggil onMeasure dalam fase measure dari siklus hidup View, yang mendahului fase layout (onLayout) dan draw (onDraw). Jika View tidak menimpa onMeasure, implementasi dari superkelas digunakan, yang menetapkan ukuran default berdasarkan background drawable atau layout_params. Panggilan super.onMeasure(widthMeasureSpec, heightMeasureSpec) hanya berfungsi untuk subkelas View standar, seperti TextView atau ImageView.
Persyaratan utama untuk onMeasure — panggilan setMeasuredDimension(int, int) harus wajib ada di akhir metode. Jika panggilan ini tidak ada, sistem menghasilkan pengecualian IllegalStateException dengan pesan bahwa View tidak menetapkan dimensi yang diukur. Dimensi akhir tersedia melalui getter getMeasuredWidth() dan getMeasuredHeight() setelah fase measure selesai.
MeasureSpec — adalah bilangan bulat 32-bit, di mana 2 bit atas mengkodekan mode pengukuran, dan 30 bit bawah — ukuran. Mode menentukan seberapa bebas View dalam memilih ukurannya. Android menyediakan tiga mode: EXACTLY, AT_MOST, dan UNSPECIFIED. Setiap mode menentukan logika pemrosesan yang berbeda dalam onMeasure.
| Mode MeasureSpec | Nilai | Perilaku |
|---|---|---|
| EXACTLY | Induk menentukan ukuran pasti | View harus pas persis dengan ukuran yang diteruskan, jika tidak ingin melampaui batas |
| AT_MOST | Induk menetapkan ukuran maksimum | View dapat memilih ukuran berapa pun dari 0 hingga maksimum yang diteruskan |
| UNSPECIFIED | Induk tidak memberikan batasan | View dapat memilih ukuran yang diinginkan tanpa batasan atas |
Untuk mengekstrak mode dan ukuran dari MeasureSpec, digunakan metode statis kelas MeasureSpec: MeasureSpec.getMode(int) mengembalikan salah satu dari tiga mode (EXACTLY, AT_MOST, UNSPECIFIED), dan MeasureSpec.getSize(int) mengembalikan ukuran numerik dalam piksel. Untuk membuat MeasureSpec sendiri, gunakan MeasureSpec.makeMeasureSpec(int size, int mode). Ketiga metode ini mencakup semua skenario bekerja dengan dimensi dalam onMeasure.
Pola standar pemrosesan MeasureSpec: jika mode EXACTLY — gunakan ukuran yang diteruskan sebagai final; jika AT_MOST — pilih minimum dari ukuran yang diinginkan (konten View) dan maksimum yang diteruskan; jika UNSPECIFIED — gunakan ukuran View yang diinginkan tanpa batasan. Pola ini menjamin perilaku yang benar di bawah batasan induk apa pun.
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val desiredWidth = 200
val desiredHeight = 100
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val width = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
else -> desiredWidth
}
val height = when (heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
else -> desiredHeight
}
setMeasuredDimension(width, height)
}
Untuk menyederhanakan logika standar, Android menyediakan metode resolveSizeAndState, yang menerima ukuran yang diinginkan, MeasureSpec, dan mengembalikan ukuran akhir dengan mode yang benar. Metode ini mengimplementasikan pola yang dijelaskan di atas dalam satu baris kode. Juga tersedia fungsi resolveSize(int size, int measureSpec), yang mengembalikan ukuran murni tanpa bit status.
Android menggunakan algoritma pengukuran dua lintasan (two-pass measure), yang menjamin bahwa setiap View dalam hierarki menerima dimensi yang benar dengan mempertimbangkan batasan induk dan preferensi elemen anak. Pada lintasan pertama, induk meneruskan MeasureSpec dengan batasan ke View anak, dan View anak menghitung dimensi yang mereka inginkan. Pada lintasan kedua, induk membuat keputusan akhir tentang dimensi.
Untuk ViewGroup, proses pengukuran lebih kompleks: induk harus terlebih dahulu mengukur semua elemen anaknya, kemudian menentukan ukurannya sendiri berdasarkan dimensi mereka. Panggilan measureChildren(int widthMeasureSpec, int heightMeasureSpec) mengiterasi semua View anak dan memanggil untuk masing-masing measure(child, childWidthSpec, childHeightSpec). Setelah mengukur semua elemen anak, ViewGroup memanggil setMeasuredDimension dengan dimensinya sendiri.
Nuansa penting: metode measure (publik, final) tidak dapat ditimpa — sebagai gantinya, onMeasure yang ditimpa. Ini menjamin bahwa sistem dapat melakukan tindakan layanan sebelum dan sesudah panggilan onMeasure, seperti memeriksa perubahan dimensi dan menghitung area kotor untuk penggambaran selanjutnya. Jika View memiliki ukuran tetap, penimpaan onMeasure mungkin tidak diperlukan.
MeasureSpec tidak hanya mencakup ukuran dan mode, tetapi juga bit status (state bits), yang dapat diakses melalui MeasureSpec.getMode(). Setelah panggilan setMeasuredDimension, status menjadi bagian dari dimensi yang diukur dari View dan dapat diperiksa melalui getMeasuredState(). Ini digunakan di ScrollView dan kontainer yang dapat digulir lainnya untuk penerusan batasan yang benar ke anak.
Mari kita lihat contoh praktis pembuatan View kustom dengan penimpaan onMeasure untuk tampilan persegi. Kelas SquareView mewarisi View dan menjamin bahwa lebar dan tinggi selalu sama, terlepas dari MeasureSpec yang diteruskan. Dalam onMeasure, sisi yang lebih kecil ditentukan dan ukuran persegi ditetapkan.
class SquareView(context: Context)
: View(context) {
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val widthSize =
MeasureSpec.getSize(widthMeasureSpec)
val heightSize =
MeasureSpec.getSize(heightMeasureSpec)
val size = minOf(widthSize, heightSize)
setMeasuredDimension(size, size)
}
}
ViewGroup memerlukan logika onMeasure yang lebih kompleks, karena pertama-tama elemen anak diukur, kemudian ukuran ViewGroup sendiri ditentukan. Contoh CascadeLayout mendistribusikan elemen anak secara kaskade dengan jarak. Setelah mengukur semua elemen anak melalui measureChildWithMargins, lebar dan tinggi total dihitung.
class CascadeLayout(context: Context)
: ViewGroup(context) {
private val cascadeOffset = 40
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
var maxWidth = 0
var totalHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec,
cascadeOffset * i,
heightMeasureSpec, 0)
maxWidth = maxOf(maxWidth,
child.measuredWidth +
cascadeOffset * i)
totalHeight += child.measuredHeight
}
setMeasuredDimension(
resolveSize(maxWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams = MarginLayoutParams(context, attrs)
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
var top = t
for (i in 0 until childCount) {
val child = getChildAt(i)
val left = l + cascadeOffset * i
child.layout(left, top,
left + child.measuredWidth,
top + child.measuredHeight)
top += child.measuredHeight
}
}
}
Melewatkan panggilan setMeasuredDimension — kesalahan paling umum. Jika pengembang menimpa onMeasure tetapi tidak memanggil setMeasuredDimension, aplikasi akan crash dengan IllegalStateException. Ini terutama sering terjadi ketika ada percabangan bersyarat dalam metode dan di salah satu cabang panggilan tidak ada. Setiap cabang kode dalam onMeasure harus diakhiri dengan panggilan setMeasuredDimension.
Mengabaikan mode AT_MOST — kesalahan paling umum kedua. Jika View dalam mode AT_MOST selalu menggunakan ukuran yang diteruskan alih-alih perhitungan berdasarkan konten, kontainer induk tidak akan dapat mendistribusikan ruang dengan benar. Misalnya, TextView dalam AT_MOST harus menghitung lebar teks dan menggunakan minimum dari lebar yang diinginkan dan yang diteruskan. Mengabaikan AT_MOST menyebabkan View menempati semua ruang yang tersedia bahkan dengan konten kecil.
Membuat objek di dalam onMeasure — kesalahan kinerja klasik. Karena onMeasure dapat dipanggil berkali-kali (pada setiap permintaan layout), membuat objek (Paint, Rect, String) di dalam metode ini mengotori memori dan memicu pengumpulan sampah. Semua objek harus dibuat sekali di konstruktor View, dan dalam onMeasure hanya logika perhitungan dimensi yang boleh dijalankan. Aturan yang sama berlaku untuk onDraw dan onLayout.
measureChildWithMargins — adalah metode protected dari ViewGroup yang mengukur satu View anak dengan mempertimbangkan MarginLayoutParams-nya. Metode ini menerima MeasureSpec induk dan pergeseran akumulasi dalam lebar dan tinggi. Secara otomatis menyesuaikan MeasureSpec untuk View anak, mengurangi padding induk dan margin anak, dan meneruskan MeasureSpec yang disesuaikan ke child.measure().
Untuk logika pengukuran lanjutan, ViewGroup dapat menimpa measureChild(View child, int parentWidthSpec, int parentHeightSpec) atau bekerja langsung dengan MeasureSpec untuk setiap elemen anak. Misalnya, LinearLayout dalam onMeasure mengiterasi semua View anak, mengukur masing-masing dengan mempertimbangkan layout_weight-nya, dan mendistribusikan ruang yang tersisa secara proporsional. Pendekatan ini memungkinkan implementasi algoritma tata letak arbitrer.
Caching hasil pengukuran melalui mekanisme cache ukuran (measure cache) tersedia melalui bendera setMeasureWithLargestChildEnabled di ViewGroup tertentu. Namun, dalam kebanyakan kasus, onMeasure dipanggil ulang pada setiap perubahan tata letak, dan caching tidak diterapkan. Di ViewGroup kustom, disarankan untuk meminimalkan perhitungan dalam onMeasure, bukan mengandalkan cache.
Pertanyaan yang Sering Diajukan
Ya, jika View kustom mewarisi langsung dari kelas View. Jika pewarisan berasal dari TextView, ImageView, atau Button dengan ukuran standarnya, onMeasure tidak perlu ditimpa. Untuk ViewGroup, penimpaan onMeasure selalu diperlukan — jika tidak, elemen anak tidak akan diukur dengan benar.
Sistem Android melempar IllegalStateException dengan teks “The View did not call setMeasuredDimension”. Pengecualian ini muncul dalam metode measure() setelah onMeasure selesai, jika dimensi akhir tetap nol. Pengecualian akan membuat aplikasi crash jika tidak ditangani melalui try-catch.
getMeasuredWidth() mengembalikan ukuran yang ditetapkan dalam onMeasure (fase pengukuran). getWidth() mengembalikan ukuran sebenarnya yang diterima View dalam onLayout setelah mempertimbangkan semua koreksi penentuan posisi. Untuk sebagian besar View, nilai-nilai ini sama, tetapi dalam ViewGroup kustom, mereka bisa berbeda.
Tidak, onMeasure ditujukan semata-mata untuk menghitung dimensi. Mengubah status, memulai animasi, bekerja dengan jaringan, atau memperbarui data dalam metode ini melanggar arsitektur Android dan dapat menyebabkan panggilan measure rekursif, karena perubahan status dapat memicu requestLayout.
ConstraintLayout secara independen mengelola pengukuran elemen anak berdasarkan batasan yang diberikan. Jika View kustom di dalam ConstraintLayout menimpa onMeasure, ia harus memproses MeasureSpec yang diteruskan dari ConstraintLayout dengan benar, jika tidak, batasan mungkin tidak berfungsi. ConstraintLayout menggunakan algoritma dua lintasan dengan WidgetContainer-nya sendiri untuk perhitungan.
Ringkasan
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.
Baca juga