Custom View in Android — tekenen en gebeurtenisafhandeling

Auteur: IT Sectr Gepubliceerd: 2026-07-20 Leestijd: 9 min

Custom View — is een aangepaste interfacecomponent in Android, gemaakt door overerving van de klasse View of zijn subklassen (Button, TextView, ImageView) en het overschrijven van de belangrijkste methoden: onDraw voor tekenen, onMeasure voor het meten van afmetingen en onTouchEvent voor het verwerken van aanrakingen. Volgens de Google Android Developer Guide (2024) wordt Custom View gebruikt wanneer standaard Android SDK-componenten het vereiste gedrag of uiterlijk niet bieden — bijvoorbeeld voor aangepaste animaties, niet-standaard vormen, gespecialiseerde grafieken en game-elementen.

Belangrijkste punten

  • Custom View — wordt gemaakt door overerving van View of zijn subklassen met overschrijving van onDraw, onMeasure, onTouchEvent.
  • Tekenen wordt uitgevoerd via Canvas API — geometrische vormen, tekst, paden, Bitmap en animaties.
  • onMeasure is verantwoordelijk voor de juiste afmetingen van de View rekening houdend met layout-parameters en padding.
  • Aangepaste XML-attributen worden gedeclareerd in res/values/attrs.xml en gelezen in de constructor van de View.
  • Aanrakingen verwerken met onTouchEvent maakt het mogelijk om ondersteuning voor gebaren, drag-and-drop en multitouch toe te voegen.

Wat is Custom View in Android?

Custom View — is een klasse die overerft van android.view.View (of een van zijn subklassen) en de systeemmethoden overschrijft om eigen teken-, meet- en gebeurtenisafhandelingslogica te implementeren. Custom View is het fundamentele mechanisme van Android voor het maken van unieke UI-componenten.

Android biedt twee benaderingen voor het maken van Custom View: overerving van View (volledig aangepast tekenen via onDraw) en overerving van een bestaande View-subklasse (bijvoorbeeld het overschrijven van Button of TextView om functionaliteit toe te voegen met behoud van het basisgedrag).

De basisconstructor van Custom View moet Context en AttributeSet accepteren — dit stelt het systeem in staat om View uit XML-code te maken. Als View alleen vanuit code wordt gebruikt, is de constructor met Context voldoende. De derde constructor met stijl is nodig voor ondersteuning van Android-thema's.

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

Minimale Custom View bestaat uit een constructor en onDraw. Voor correcte werking in het Android Layout-systeem moet echter ook onMeasure worden overschreven — anders kan View worden weergegeven met nulafmetingen of onjuist reageren op layout-parameters wrap_content.

Volgens Google I/O 2023 worden aangepaste Views gebruikt in 65% van de apps in de top 100 van Google Play. De meest voorkomende redenen voor het maken van Custom View: aangepaste animaties (35%), aangepaste grafieken en diagrammen (25%), gespecialiseerde bedieningselementen (20%) en branded componenten (20%).

onMeasure en onLayout: afmetingen beheren

onMeasure — is de methode waarin View het systeem zijn gewenste afmetingen meedeelt op basis van de doorgegeven MeasureSpec (ouderlijke beperkingen). Zonder correcte implementatie van onMeasure kan View nul hoogte hebben bij wrap_content of het volledige schermoppervlak innemen bij match_parent.

MeasureSpec bestaat uit een modus (EXACTLY, AT_MOST, UNSPECIFIED) en een waarde. EXACTLY — de ouder heeft een exacte afmeting ingesteld (match_parent of vaste waarde). AT_MOST — de ouder heeft een maximum ingesteld (wrap_content). UNSPECIFIED — zonder beperkingen (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 — is een handige Android-methode die de implementatie van onMeasure vereenvoudigt. Het ontvangt de gewenste afmeting en MeasureSpec en retourneert de juiste waarde: voor EXACTLY — exacte afmeting, voor AT_MOST — minimum van gewenst en maximum, voor UNSPECIFIED — gewenste afmeting.

onLayout voor View (niet ViewGroup) wordt meestal niet overschreven — het wordt aangeroepen door de ouder voor het positioneren van kinderelementen. Voor ViewGroup is onLayout verplicht — hierin moet layout() worden aangeroepen voor elke kind-View.

onDraw en Canvas API: tekenen

onDraw — het hart van Custom View. Deze methode wordt door Android aangeroepen bij elke hertekening van de View. Het ontvangt een Canvas-object waarmee al het tekenen wordt uitgevoerd: geometrische vormen, tekst, paden, Bitmap en animaties.

Canvas API biedt methoden voor alle basisbewerkingen: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Voor het configureren van de stijl wordt Paint gebruikt — een object dat kleur, dikte, vulstijl, lettertype, schaduwen en effecten definieert.

kotlin
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    // Achtergrond
    canvas.drawRect(0f, 0f,
        width.toFloat(), height.toFloat(), bgPaint)

    // Tekst
    canvas.drawText("Aangepaste weergave",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

    // Cirkel
    canvas.drawCircle(
        width / 2f,
        height / 2f,
        radius, circlePaint)

    // Pad (Bezier-curve)
    val path = Path().apply {
        moveTo(0f, height.toFloat())
        quadTo(width / 2f, 0f,
            width.toFloat(), height.toFloat())
    }
    canvas.drawPath(path, pathPaint)
}

Paint — is de configurator van de tekenstijl. Anti-aliasing (Paint.ANTI_ALIAS_FLAG) is verplicht voor gladde randen. Subpixel text (SUBPIXEL_TEXT_FLAG) verbetert de tekstkwaliteit. Style.FILL, STROKE en FILL_AND_STROKE bepalen of de vorm wordt gevuld, omlijnd of beide.

Belangrijk: maak geen objecten Paint, Path, Rect in onDraw — dit leidt tot allocaties in de tekenlus en activeert garbage collection, wat de FPS verlaagt. Maak alle tekenobjecten in de constructor of in de init-methode. Gebruik voor animatie ValueAnimator of ObjectAnimator met invalidate() om hertekening te starten.

Aangepaste XML-attributen in attrs.xml

Aangepaste attributen maken het mogelijk om Custom View te configureren vanuit XML-code, net zoals standaard android:layout_width of android:background. Attributen worden gedeclareerd in het bestand res/values/attrs.xml met vermelding van naam, type en optionele standaardwaarde.

Attribuuttypen: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (verwijzing naar resource). Voor elk type parseert Android automatisch de waarde uit XML en geeft deze door aan 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>

In de constructor van Custom View worden attributen gelezen via context.obtainStyledAttributes, die een TypedArray retourneert. TypedArray biedt getypeerde toegangsmethoden: getColor, getDimension, getString, getBoolean, getInt. Na het lezen moet recycle() worden aangeroepen op TypedArray om resources vrij te geven.

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

Gebruik in XML: voeg de naamruimte app toe (xmlns:app="http://schemas.android.com/apk/res-auto") en gebruik aangepaste attributen als app:circleColor="@color/red". Android Studio zal attribuuttypen automatisch aanvullen en controleren als de declaratie in attrs.xml correct is.

Aanrakingen verwerken met onTouchEvent

onTouchEvent — de methode die wordt aangeroepen bij elke aanraking van de View. Het ontvangt een MotionEvent-object met informatie over het gebeurtenistype (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), coördinaten, druk en aantal vingers (multitouch).

Gebruik voor het verwerken van complexe gebaren (vegen, schalen, lang indrukken) GestureDetector of ScaleGestureDetector in combinatie met onTouchEvent. GestureDetector vereenvoudigt de herkenning van onSingleTapUp, onFling, onLongPress, onDoubleTap en andere standaardgebaren.

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 — bepalen of een aanraking een specifiek gebied van de View heeft geraakt. Gebruik voor rechthoekige gebieden Rect.contains(x, y). Voor cirkels — controleer de afstand tot het midden: sqrt(dx^2 + dy^2) < radius. Voor willekeurige vormen — Path.op() of Region.contains().

Multitouch wordt verwerkt via MotionEvent.getPointerCount() en getPointerId(index). Elke vinger krijgt een uniek ID dat behouden blijft van ACTION_DOWN tot ACTION_POINTER_UP. ACTION_MOVE kan gegevens bevatten van alle actieve vingers — gebruik getHistoricalX/Y voor interpolatie van beweging.

Prestatieoptimalisatie van Custom View

Custom View kan een prestatieknelpunt worden als onDraw vaak wordt aangeroepen (animatie, scrollen) of zware bewerkingen bevat. Android biedt verschillende mechanismen voor het optimaliseren van het tekenen van aangepaste componenten.

Layer type voor complex tekenwerk

View.setLayerType maakt het mogelijk om de rendering van View te schakelen naar een software (LAYER_TYPE_SOFTWARE) of hardware (LAYER_TYPE_HARDWARE) laag. Software layer is nuttig voor complexe Canvas-graphics die niet worden ondersteund door hardwareversnelling — bijvoorbeeld drawTextOnPath of complexe Path-effecten.

invalidate alleen het benodigde gebied

invalidate(Rect) — hertekent alleen het opgegeven gebied van de View, niet de hele component. Dit is cruciaal voor grote Custom Views (grafieken, kaarten, tekendoeken), waar volledige hertekening van elk frame FPS-verlies veroorzaakt. Gebruik postInvalidateOnAnimation() voor synchronisatie met vsync.

kotlin
class EfficientCustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    // Objecten eenmalig gemaakt in constructor
    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)
        // Herteken alleen gebied rond het punt
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

Hardwareversnelling is standaard ingeschakeld op Android 3.0+ (API 11+). Canvas-bewerkingen drawCircle, drawRect, drawBitmap worden hardwareversneld en uitgevoerd op de GPU. Echter, drawTextOnPath, drawVertices, complexe clipping-bewerkingen worden niet versneld — gebruik hiervoor LAYER_TYPE_SOFTWARE.

Volgens Android Performance Patterns (Google, 2023) zijn de belangrijkste oorzaken van lag in Custom View: allocaties in onDraw (objecten maken per frame), hertekenen van de hele View bij wijziging van één element en het ontbreken van LayerType.HARDWARE voor statische inhoud. Het verhelpen van deze drie problemen geeft een FPS-verbetering van 30 naar 60 in de meeste scenario's.

MethodeDoelAanroepfrequentie
onDrawInhoud van View tekenenBij elke invalidate()
onMeasureAfmetingen van View bepalenBij wijziging van layout
onLayoutKind-Views positionerenNa onMeasure
onTouchEventAanrakingen verwerkenBij elke aanraking
onSizeChangedReageren op formaatwijzigingBij eerste layout en wijziging

Veelgestelde vragen

Wanneer Custom View gebruiken in plaats van Compose?

Custom View (Canvas API) is gerechtvaardigd wanneer maximale prestaties voor aangepast tekenen nodig zijn (grafieken, videobewerkers, kaarten), integratie met bestaande View-based code of ondersteuning voor Android-versies onder API 21. Jetpack Compose is de moderne benadering voor de meeste nieuwe projecten, gebruikmakend van declaratieve UI en beter geschikt voor dynamische interfaces.

Is het overschrijven van onMeasure verplicht?

Ja, als View wrap_content gebruikt in XML-code. Zonder onMeasure werkt wrap_content als match_parent, omdat de standaardimplementatie van View.onMeasure geen standaardafmeting instelt. Als View altijd een vaste afmeting of match_parent heeft, kan onMeasure worden overgeslagen.

Hoe animeer ik een Custom View?

Gebruik ValueAnimator of ObjectAnimator om eigenschappen van View te wijzigen (kleur, straal, positie) en roep invalidate() aan in de animatie-callback voor hertekening. ValueAnimator werkt op de hoofdthread en is gesynchroniseerd met vsync. Gebruik voor complexe animaties (fysica, deeltjes) Choreographer.FrameCallback of Android Animation Framework.

Hoe verwerk ik multitouch in Custom View?

Gebruik in onTouchEvent MotionEvent.getPointerCount() om het aantal vingers te bepalen en getPointerId(i) om elke vinger te volgen via een uniek ID. ACTION_POINTER_DOWN / ACTION_POINTER_UP — gebeurtenissen voor toevoegen/verwijderen van vinger. Gebruik voor schalen ScaleGestureDetector samen met onTouchEvent — het vereenvoudigt de detectie van pinch-to-zoom.

Kan Custom View worden gebruikt in Jetpack Compose?

Ja, via AndroidView — een composable-functie die een View-based element in de Compose-hiërarchie invoegt. AndroidView ontvangt een fabriek voor het maken van View en een update-callback voor synchronisatie van de status. Dit maakt hergebruik van bestaande Custom Views in nieuwe Compose-projecten mogelijk zonder volledig herschrijven.

Samenvatting

  • Custom View — aangepaste Android-component, gemaakt door overerving van View met overschrijving van onDraw, onMeasure en onTouchEvent voor aangepast tekenen en verwerking.
  • onDraw en Canvas API — basis van tekenen: geometrische vormen, tekst, Bitmap, paden — met configuratie via Paint voor kleur, stijl en lettertype.
  • onMeasure is belangrijk voor correcte werking van wrap_content; gebruikt MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) en resolveSize voor het berekenen van afmetingen.
  • Aangepaste attributen via attrs.xml en TypedArray maken configuratie van Custom View vanuit XML-code mogelijk met autocomplete-ondersteuning in Android Studio.
  • onTouchEvent met GestureDetector zorgt voor verwerking van aanrakingen, gebaren en multitouch, en hit-testing bepaalt het interactiegebied.
  • Prestaties: maak objecten eenmalig in de constructor, gebruik invalidate(Rect) voor gedeeltelijk hertekenen en LayerType voor complexe effecten.
  • Custom View integreert met Jetpack Compose via AndroidView, waardoor kant-en-klare componenten kunnen worden hergebruikt in nieuwe Compose-projecten.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook