Custom View di Android — menggambar dan memproses event

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

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 — dibuat dengan mewarisi View atau subkelasnya dengan menimpa onDraw, onMeasure, onTouchEvent.
  • Menggambar dilakukan melalui Canvas API — bentuk geometris, teks, jalur, Bitmap dan animasi.
  • onMeasure bertanggung jawab atas dimensi View yang benar dengan mempertimbangkan parameter layout dan padding.
  • Atribut XML khusus dideklarasikan di res/values/attrs.xml dan dibaca di konstruktor View.
  • Pemrosesan sentuhan onTouchEvent memungkinkan penambahan dukungan untuk gerakan, drag-and-drop dan multitouch.

Apa itu Custom View di Android?

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.

kotlin
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 dan onLayout: mengelola dimensi

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

kotlin
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 dan Canvas API: menggambar

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.

kotlin
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 XML khusus di attrs.xml

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.

xml
<!-- 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.

kotlin
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.

Memproses sentuhan dengan onTouchEvent

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.

kotlin
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.

Optimasi kinerja Custom View

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.

Layer type untuk penggambaran kompleks

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 hanya area yang diperlukan

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.

kotlin
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.

MetodeTujuanFrekuensi panggilan
onDrawMenggambar konten ViewSetiap invalidate()
onMeasureMenentukan dimensi ViewSaat layout berubah
onLayoutMemposisikan View anakSetelah onMeasure
onTouchEventMemproses sentuhanSetiap sentuhan
onSizeChangedReaksi terhadap perubahan ukuranSaat layout pertama dan perubahan

Pertanyaan yang sering diajukan

Kapan menggunakan Custom View daripada Compose?

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.

Apakah menimpa onMeasure wajib?

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.

Bagaimana cara menganimasi Custom View?

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.

Bagaimana cara memproses multitouch di Custom View?

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.

Bisakah Custom View digunakan di Jetpack Compose?

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

  • Custom View — komponen Android khusus, dibuat dengan mewarisi View dan menimpa onDraw, onMeasure dan onTouchEvent untuk penggambaran dan pemrosesan khusus.
  • onDraw dan Canvas API — dasar menggambar: bentuk geometris, teks, Bitmap, jalur — dengan konfigurasi melalui Paint untuk warna, gaya dan font.
  • onMeasure penting untuk kerja wrap_content yang benar; menggunakan MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) dan resolveSize untuk menghitung dimensi.
  • Atribut khusus melalui attrs.xml dan TypedArray memungkinkan konfigurasi Custom View dari kode XML dengan dukungan autocomplete di Android Studio.
  • onTouchEvent dengan GestureDetector menyediakan pemrosesan sentuhan, gerakan dan multitouch, dan hit-testing menentukan area interaksi.
  • Kinerja: buat objek sekali di konstruktor, gunakan invalidate(Rect) untuk penggambaran ulang sebagian dan LayerType untuk efek kompleks.
  • Custom View terintegrasi dengan Jetpack Compose melalui AndroidView, memungkinkan penggunaan kembali komponen yang sudah jadi di proyek Compose baru.

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