Custom View — adalah komponen antarmuka khusus di Android, dibuat dengan mewarisi kelas View atau subkelasnya (Button, TextView, ImageView) dan menimpa metode kunci: onDraw untuk menggambar, onMeasure untuk mengukur dimensi dan onTouchEvent untuk memproses sentuhan. Menurut Google Android Developer Guide (2024), Custom View digunakan ketika komponen standar Android SDK tidak menyediakan perilaku atau tampilan yang diperlukan — misalnya, untuk animasi khusus, bentuk tidak standar, grafik khusus, dan elemen game.
Poin utama
Custom View — adalah kelas yang mewarisi android.view.View (atau salah satu subkelasnya) dan menimpa metode sistem untuk mengimplementasikan logika menggambar, mengukur, dan memproses event sendiri. Custom View adalah mekanisme fundamental Android untuk membuat komponen UI yang unik.
Android menyediakan dua pendekatan untuk membuat Custom View: mewarisi dari View (menggambar khusus sepenuhnya melalui onDraw) dan mewarisi dari subkelas View yang ada (misalnya, menimpa Button atau TextView untuk menambahkan fungsionalitas sambil mempertahankan perilaku dasar).
Konstruktor dasar Custom View harus menerima Context dan AttributeSet — ini memungkinkan sistem membuat View dari kode XML. Jika View hanya akan digunakan dari kode, konstruktor dengan Context sudah cukup. Konstruktor ketiga dengan gaya diperlukan untuk mendukung tema Android.
class CustomView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
strokeWidth = 4f
style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(width / 2f,
height / 2f,
50f, paint)
}
}
Custom View minimal terdiri dari konstruktor dan onDraw. Namun, untuk bekerja dengan benar dalam sistem Layout Android, onMeasure juga harus ditimpa — jika tidak, View dapat ditampilkan dengan dimensi nol atau bereaksi tidak benar terhadap parameter layout wrap_content.
Menurut Google I/O 2023, View khusus digunakan di 65% aplikasi dari top 100 Google Play. Alasan paling umum membuat Custom View: animasi khusus (35%), grafik dan diagram khusus (25%), elemen kontrol khusus (20%), dan komponen bermerek (20%).
onMeasure — adalah metode di mana View memberi tahu sistem tentang dimensi yang diinginkan berdasarkan MeasureSpec yang diterima (batasan induk). Tanpa implementasi onMeasure yang benar, View dapat memiliki tinggi nol dengan wrap_content atau menempati seluruh area layar dengan match_parent.
MeasureSpec terdiri dari mode (EXACTLY, AT_MOST, UNSPECIFIED) dan nilai. EXACTLY — induk menetapkan ukuran pasti (match_parent atau nilai tetap). AT_MOST — induk menetapkan maksimum (wrap_content). UNSPECIFIED — tanpa batasan (ScrollView, ListView).
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
) {
val desiredWidth = paddingLeft + paddingRight
+ DEFAULT_WIDTH
val desiredHeight = paddingTop + paddingBottom
+ DEFAULT_HEIGHT
val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
val modeWidth = MeasureSpec.getMode(widthMeasureSpec)
val resultWidth = when (modeWidth) {
MeasureSpec.EXACTLY -> measuredWidth
MeasureSpec.AT_MOST ->
desiredWidth.coerceAtMost(measuredWidth)
else -> desiredWidth
}
val resultHeight = resolveSize(desiredHeight,
heightMeasureSpec)
setMeasuredDimension(resultWidth, resultHeight)
}
resolveSize — adalah metode utilitas Android yang menyederhanakan implementasi onMeasure. Ia menerima ukuran yang diinginkan dan MeasureSpec dan mengembalikan nilai yang benar: untuk EXACTLY — ukuran pasti, untuk AT_MOST — minimum dari yang diinginkan dan maksimum, untuk UNSPECIFIED — ukuran yang diinginkan.
onLayout untuk View (bukan ViewGroup) biasanya tidak ditimpa — ia dipanggil oleh induk untuk memposisikan elemen anak. Untuk ViewGroup, onLayout wajib — di dalamnya layout() harus dipanggil untuk setiap View anak.
onDraw — jantung Custom View. Metode ini dipanggil oleh Android setiap kali View digambar ulang. Ia menerima objek Canvas yang melalui semua penggambaran dilakukan: bentuk geometris, teks, jalur, Bitmap dan animasi.
Canvas API menyediakan metode untuk semua operasi dasar: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Untuk mengonfigurasi gaya, Paint digunakan — objek yang menentukan warna, ketebalan, gaya isian, font, bayangan dan efek.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Latar belakang
canvas.drawRect(0f, 0f,
width.toFloat(), height.toFloat(), bgPaint)
// Teks
canvas.drawText("Tampilan khusus",
paddingLeft.toFloat(),
paddingTop.toFloat() + textPaint.textSize,
textPaint)
// Lingkaran
canvas.drawCircle(
width / 2f,
height / 2f,
radius, circlePaint)
// Jalur (kurva Bezier)
val path = Path().apply {
moveTo(0f, height.toFloat())
quadTo(width / 2f, 0f,
width.toFloat(), height.toFloat())
}
canvas.drawPath(path, pathPaint)
}
Paint — adalah konfigurator gaya menggambar. Anti-aliasing (Paint.ANTI_ALIAS_FLAG) wajib untuk tepi yang halus. Subpixel text (SUBPIXEL_TEXT_FLAG) meningkatkan kualitas teks. Style.FILL, STROKE dan FILL_AND_STROKE menentukan apakah bentuk akan diisi, digaris, atau keduanya.
Penting: jangan buat objek Paint, Path, Rect di onDraw — ini menyebabkan alokasi dalam loop menggambar dan memicu garbage collection, yang menurunkan FPS. Buat semua objek gambar di konstruktor atau metode init. Untuk animasi, gunakan ValueAnimator atau ObjectAnimator dengan invalidate() untuk memulai penggambaran ulang.
Atribut khusus memungkinkan konfigurasi Custom View dari kode XML seperti halnya android:layout_width atau android:background standar. Atribut dideklarasikan di file res/values/attrs.xml dengan menyebutkan nama, tipe, dan nilai default opsional.
Tipe atribut: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (referensi ke resource). Untuk setiap tipe, Android secara otomatis mengurai nilai dari XML dan meneruskannya ke TypedArray.
<!-- res/values/attrs.xml -->
<resources>
<declare-styleable name="CustomView">
<attr name="circleColor"
format="color" />
<attr name="circleRadius"
format="dimension" />
<attr name="labelText"
format="string" />
<attr name="showAnimation"
format="boolean" />
</declare-styleable>
</resources>
Di konstruktor Custom View, atribut dibaca melalui context.obtainStyledAttributes, yang mengembalikan TypedArray. TypedArray menyediakan metode akses yang diketik: getColor, getDimension, getString, getBoolean, getInt. Setelah membaca, recycle() harus dipanggil pada TypedArray untuk membebaskan resource.
init {
val typedArray = context.obtainStyledAttributes(
attrs,
R.styleable.CustomView
)
circleColor = typedArray.getColor(
R.styleable.CustomView_circleColor,
Color.BLUE
)
circleRadius = typedArray.getDimension(
R.styleable.CustomView_circleRadius,
50f
)
labelText = typedArray.getString(
R.styleable.CustomView_labelText
) ?: "Default"
typedArray.recycle()
}
Penggunaan dalam XML: tambahkan namespace app (xmlns:app="http://schemas.android.com/apk/res-auto") dan gunakan atribut khusus sebagai app:circleColor="@color/red". Android Studio akan melengkapi otomatis dan memeriksa tipe atribut jika deklarasi di attrs.xml benar.
onTouchEvent — metode yang dipanggil pada setiap sentuhan View. Ia menerima objek MotionEvent dengan informasi tentang tipe event (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), koordinat, tekanan, dan jumlah jari (multitouch).
Untuk memproses gerakan kompleks (gesek, skala, tekan lama), gunakan GestureDetector atau ScaleGestureDetector dalam kombinasi dengan onTouchEvent. GestureDetector menyederhanakan pengenalan onSingleTapUp, onFling, onLongPress, onDoubleTap dan gerakan standar lainnya.
private val gestureDetector = GestureDetector(
context, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean {
handleTap(e.x, e.y)
return true
}
})
override fun onTouchEvent(event: MotionEvent): Boolean {
val handled = gestureDetector.onTouchEvent(event)
when (event.action) {
MotionEvent.ACTION_MOVE -> {
currentX = event.x
currentY = event.y
invalidate()
return true
}
}
return handled || super.onTouchEvent(event)
}
Hit-testing — menentukan apakah sentuhan mengenai area tertentu dari View. Untuk area persegi panjang, gunakan Rect.contains(x, y). Untuk lingkaran — periksa jarak dari pusat: sqrt(dx^2 + dy^2) < radius. Untuk bentuk arbitrer — Path.op() atau Region.contains().
Multitouch diproses melalui MotionEvent.getPointerCount() dan getPointerId(index). Setiap jari mendapatkan ID unik yang dipertahankan dari ACTION_DOWN hingga ACTION_POINTER_UP. ACTION_MOVE dapat berisi data semua jari aktif — gunakan getHistoricalX/Y untuk interpolasi gerakan.
Custom View dapat menjadi bottleneck kinerja jika onDraw sering dipanggil (animasi, scroll) atau mengandung operasi berat. Android menyediakan beberapa mekanisme untuk mengoptimalkan penggambaran komponen khusus.
View.setLayerType memungkinkan peralihan rendering View ke layer software (LAYER_TYPE_SOFTWARE) atau hardware (LAYER_TYPE_HARDWARE). Software layer berguna untuk grafis Canvas kompleks yang tidak didukung oleh akselerasi hardware — misalnya, drawTextOnPath atau efek Path kompleks.
invalidate(Rect) — menggambar ulang hanya area View yang ditentukan, bukan seluruh komponen. Ini sangat penting untuk Custom View besar (grafik, peta, kanvas gambar), di mana penggambaran ulang penuh setiap frame menyebabkan penurunan FPS. Gunakan postInvalidateOnAnimation() untuk sinkronisasi dengan vsync.
class EfficientCustomView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
// Objek dibuat sekali di konstruktor
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val rect = Rect()
private val position = PointF()
fun updatePosition(x: Float, y: Float) {
position.set(x, y)
// Gambar ulang hanya area di sekitar titik
rect.set(
(x - 10).toInt(),
(y - 10).toInt(),
(x + 10).toInt(),
(y + 10).toInt()
)
invalidate(rect)
}
}
Akselerasi hardware diaktifkan secara default di Android 3.0+ (API 11+). Operasi Canvas drawCircle, drawRect, drawBitmap diakselerasi hardware dan dijalankan di GPU. Namun, drawTextOnPath, drawVertices, operasi clipping kompleks tidak diakselerasi — gunakan LAYER_TYPE_SOFTWARE untuk mereka.
Menurut Android Performance Patterns (Google, 2023), penyebab utama lag Custom View adalah alokasi di onDraw (membuat objek setiap frame), penggambaran ulang seluruh View saat satu elemen berubah, dan tidak adanya LayerType.HARDWARE untuk konten statis. Memperbaiki tiga masalah ini memberikan peningkatan FPS dari 30 ke 60 di sebagian besar skenario.
| Metode | Tujuan | Frekuensi panggilan |
|---|---|---|
| onDraw | Menggambar konten View | Setiap invalidate() |
| onMeasure | Menentukan dimensi View | Saat layout berubah |
| onLayout | Memposisikan View anak | Setelah onMeasure |
| onTouchEvent | Memproses sentuhan | Setiap sentuhan |
| onSizeChanged | Reaksi terhadap perubahan ukuran | Saat layout pertama dan perubahan |
Pertanyaan yang sering diajukan
Custom View (Canvas API) dibenarkan ketika diperlukan kinerja maksimal penggambaran khusus (grafik, editor video, peta), integrasi dengan kode View-based yang ada, atau dukungan versi Android di bawah API 21. Jetpack Compose adalah pendekatan modern untuk sebagian besar proyek baru, menggunakan UI deklaratif dan lebih cocok untuk antarmuka dinamis.
Ya, jika View menggunakan wrap_content dalam kode XML. Tanpa onMeasure, wrap_content akan bekerja seperti match_parent, karena implementasi standar View.onMeasure tidak menetapkan ukuran default. Jika View selalu memiliki ukuran tetap atau match_parent, onMeasure dapat dilewati.
Gunakan ValueAnimator atau ObjectAnimator untuk mengubah properti View (warna, radius, posisi) dan panggil invalidate() di callback animasi untuk penggambaran ulang. ValueAnimator bekerja di thread utama dan disinkronkan dengan vsync. Untuk animasi kompleks (fisika, partikel), gunakan Choreographer.FrameCallback atau Android Animation Framework.
Di onTouchEvent, gunakan MotionEvent.getPointerCount() untuk menentukan jumlah jari dan getPointerId(i) untuk melacak setiap jari berdasarkan ID unik. ACTION_POINTER_DOWN / ACTION_POINTER_UP — event penambahan/penghapusan jari. Untuk penskalaan, gunakan ScaleGestureDetector bersama onTouchEvent — ia menyederhanakan deteksi pinch-to-zoom.
Ya, melalui AndroidView — fungsi composable yang menyisipkan elemen View-based ke dalam hierarki Compose. AndroidView menerima pabrik untuk membuat View dan callback update untuk sinkronisasi status. Ini memungkinkan penggunaan kembali Custom View yang ada di proyek Compose baru tanpa menulis ulang sepenuhnya.
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