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 — 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.
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 — 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).
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 — 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.
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 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.
<!-- 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.
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.
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.
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.
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.
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(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.
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.
| Methode | Doel | Aanroepfrequentie |
|---|---|---|
| onDraw | Inhoud van View tekenen | Bij elke invalidate() |
| onMeasure | Afmetingen van View bepalen | Bij wijziging van layout |
| onLayout | Kind-Views positioneren | Na onMeasure |
| onTouchEvent | Aanrakingen verwerken | Bij elke aanraking |
| onSizeChanged | Reageren op formaatwijziging | Bij eerste layout en wijziging |
Veelgestelde vragen
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.
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.
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.
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.
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
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.
Lees ook