Custom View in Android — Wesen, Zeichnen und Ereignisbehandlung

Autor: IT Sectr Veröffentlicht: 2026-07-20 Lesezeit: 9 Min.

Custom View ist eine benutzerdefinierte Oberflächenkomponente in Android, die durch Vererbung von der View-Klasse oder ihren Unterklassen (Button, TextView, ImageView) und Überschreiben der Schlüsselmethoden erstellt wird: onDraw zum Zeichnen, onMeasure zum Messen von Größen und onTouchEvent zur Behandlung von Berührungen. Laut Google Android Developer Guide (2024) wird Custom View verwendet, wenn die Standard-Android-SDK-Komponenten nicht das erforderliche Verhalten oder Aussehen bieten — zum Beispiel für benutzerdefinierte Animationen, nicht standardmäßige Formen, spezialisierte Diagramme und Spielelemente.

Wichtige Punkte

  • Custom View — wird durch Vererbung von View oder seinen Unterklassen mit Überschreiben von onDraw, onMeasure, onTouchEvent erstellt.
  • Das Zeichnen erfolgt über die Canvas API — geometrische Formen, Text, Pfade, Bitmap und Animation.
  • onMeasure kümmert sich um die korrekten View-Größen unter Berücksichtigung von Layout-Parametern und Padding.
  • Benutzerdefinierte XML-Attribute werden in res/values/attrs.xml deklariert und im View-Konstruktor gelesen.
  • Die Berührungsbehandlung über onTouchEvent ermöglicht das Hinzufügen von Gestenunterstützung, Drag-and-Drop und Multi-Touch.

Was ist Custom View in Android?

Custom View ist eine Klasse, die von android.view.View (oder einer ihrer Unterklassen) erbt und Systemmethoden überschreibt, um eine benutzerdefinierte Zeichen-, Mess- und Ereignisbehandlungslogik zu implementieren. Custom View ist ein grundlegender Mechanismus von Android zur Erstellung einzigartiger UI-Komponenten.

Android bietet zwei Ansätze zur Erstellung von Custom View: Vererbung von View (vollständig benutzerdefiniertes Zeichnen über onDraw) und Vererbung von einer vorhandenen View-Unterklasse (z. B. Überschreiben von Button oder TextView, um Funktionalität hinzuzufügen und dabei das Basisverhalten beizubehalten).

Basis-Konstruktor eines Custom View muss Context und AttributeSet akzeptieren — dies ermöglicht dem System, die View aus XML-Markup zu erstellen. Wenn die View nur aus Code verwendet wird, ist ein Konstruktor mit Context ausreichend. Ein dritter Konstruktor mit Stil wird für die Android-Theme-Unterstützung benötigt.

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

Minimaler Custom View besteht aus einem Konstruktor und onDraw. Für korrektes Arbeiten im Android-Layout-System muss jedoch auch onMeasure überschrieben werden — andernfalls kann die View mit Null-Größen angezeigt werden oder falsch auf wrap_content-Layout-Parameter reagieren.

Laut Google I/O 2023 werden Custom Views in 65 % der Top-100-Google-Play-Apps verwendet. Die häufigsten Gründe für die Erstellung eines Custom View: benutzerdefinierte Animationen (35 %), benutzerdefinierte Diagramme (25 %), spezialisierte Steuerelemente (20 %) und Markenkomponenten (20 %).

onMeasure und onLayout: Größenverwaltung

onMeasure ist die Methode, in der die View ihre gewünschten Abmessungen basierend auf dem übergebenen MeasureSpec (Eltern-Einschränkungen) meldet. Ohne eine korrekte onMeasure-Implementierung kann die View bei wrap_content eine Höhe von Null haben oder bei match_parent den gesamten Bildschirmbereich einnehmen.

MeasureSpec besteht aus einem Modus (EXACTLY, AT_MOST, UNSPECIFIED) und einem Wert. EXACTLY — der Elternteil hat eine genaue Größe festgelegt (match_parent oder fester Wert). AT_MOST — der Elternteil hat ein Maximum festgelegt (wrap_content). UNSPECIFIED — keine Einschränkungen (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 ist eine Android-Dienstprogrammmethode, die die onMeasure-Implementierung vereinfacht. Sie nimmt die gewünschte Größe und MeasureSpec entgegen und gibt den korrekten Wert zurück: für EXACTLY — die genaue Größe, für AT_MOST — das Minimum von gewünscht und maximal, für UNSPECIFIED — die gewünschte Größe.

onLayout wird für View (nicht ViewGroup) normalerweise nicht überschrieben — es wird vom Elternteil aufgerufen, um untergeordnete Elemente zu positionieren. Für ViewGroup ist onLayout obligatorisch — layout() muss für jede untergeordnete View aufgerufen werden.

onDraw und Canvas API: Zeichnen

onDraw ist das Herzstück von Custom View. Diese Methode wird von Android bei jeder Neuzeichnung der View aufgerufen. Ihr wird ein Canvas-Objekt übergeben, über das das gesamte Zeichnen erfolgt: geometrische Formen, Text, Pfade, Bitmap und Animationen.

Canvas API bietet Methoden für alle grundlegenden Operationen: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Zur Konfiguration des Stils wird Paint verwendet — ein Objekt, das Farbe, Dicke, Füllstil, Schriftart, Schatten und Effekte definiert.

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

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

    // Text
    canvas.drawText("Custom View",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

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

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

Paint ist der Zeichenstil-Konfigurator. Anti-Aliasing (Paint.ANTI_ALIAS_FLAG) ist für glatte Kanten obligatorisch. Subpixel-Text (SUBPIXEL_TEXT_FLAG) verbessert die Textqualität. Style.FILL, STROKE und FILL_AND_STROKE bestimmen, ob die Form gefüllt, umrandet oder beides wird.

Wichtig: Erstellen Sie keine Paint-, Path-, Rect-Objekte in onDraw — dies führt zu Allokationen in der Zeichenschleife und löst Garbage Collection aus, was die FPS reduziert. Erstellen Sie alle Zeichenobjekte im Konstruktor oder in der init-Methode. Für Animationen verwenden Sie ValueAnimator oder ObjectAnimator mit invalidate(), um die Neuzeichnung auszulösen.

Benutzerdefinierte XML-Attribute in attrs.xml

Benutzerdefinierte Attribute ermöglichen die Konfiguration eines Custom View aus XML-Markup, genau wie standardmäßige android:layout_width oder android:background. Die Attribute werden in der Datei res/values/attrs.xml mit Name, Typ und optionalem Standardwert deklariert.

Attributtypen: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (Ressourcenreferenz). Für jeden Typ parst Android automatisch den Wert aus XML und übergibt ihn an 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="boolesch" />
    </declare-styleable>
</resources>

Im Custom-View-Konstruktor werden die Attribute über context.obtainStyledAttributes gelesen, das ein TypedArray zurückgibt. TypedArray bietet typisierte Zugriffsmethoden: getColor, getDimension, getString, getBoolean, getInt. Nach dem Lesen muss recycle() auf dem TypedArray aufgerufen werden, um Ressourcen freizugeben.

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

Verwendung in XML: Fügen Sie den app-Namespace hinzu (xmlns:app="http://schemas.android.com/apk/res-auto") und verwenden Sie benutzerdefinierte Attribute wie app:circleColor="@color/red". Android Studio vervollständigt und validiert die Attributtypen automatisch, wenn die Deklaration in attrs.xml korrekt ist.

Berührungsbehandlung mit onTouchEvent

onTouchEvent ist die Methode, die bei jeder Berührung der View aufgerufen wird. Sie erhält ein MotionEvent-Objekt mit Informationen über den Ereignistyp (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), Koordinaten, Druck und Anzahl der Finger (Multi-Touch).

Zur Behandlung komplexer Gesten (Wischen, Zwei-Finger-Zoom, langes Drücken) verwenden Sie GestureDetector oder ScaleGestureDetector in Kombination mit onTouchEvent. GestureDetector vereinfacht die Erkennung von onSingleTapUp, onFling, onLongPress, onDoubleTap und anderen Standardgesten.

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 — Bestimmen, ob eine Berührung in einem bestimmten Bereich der View gelandet ist. Für rechteckige Bereiche verwenden Sie Rect.contains(x, y). Für Kreise — Abstandsprüfung vom Mittelpunkt: sqrt(dx^2 + dy^2) < radius. Für beliebige Formen — Path.op() oder Region.contains().

Multi-Touch wird über MotionEvent.getPointerCount() und getPointerId(index) behandelt. Jeder Finger erhält eine eindeutige ID, die von ACTION_DOWN bis ACTION_POINTER_UP erhalten bleibt. ACTION_MOVE kann Daten aller aktiven Finger enthalten — verwenden Sie getHistoricalX/Y zur Bewegungsinterpolation.

Performance-Optimierung von Custom View

Custom View kann zu einem Leistungsengpass werden, wenn onDraw häufig aufgerufen wird (Animation, Scrollen) oder schwere Operationen enthält. Android bietet mehrere Mechanismen zur Optimierung des Renderings benutzerdefinierter Komponenten.

Layer-Typ für komplexes Zeichnen

View.setLayerType ermöglicht das Umschalten des View-Renderings auf eine Software-Ebene (LAYER_TYPE_SOFTWARE) oder Hardware-Ebene (LAYER_TYPE_HARDWARE). Die Software-Ebene ist nützlich für komplexe Canvas-Grafiken, die nicht von der Hardwarebeschleunigung unterstützt werden — z. B. drawTextOnPath oder komplexe Path-Effekte.

Nur den benötigten Bereich invalidieren

invalidate(Rect) zeichnet nur den angegebenen Bereich der View neu, nicht die gesamte Komponente. Dies ist besonders wichtig für große Custom Views (Grafiken, Karten, Zeichenflächen), wo die vollständige Neuzeichnung jedes Frames zu FPS-Einbrüchen führt. Verwenden Sie postInvalidateOnAnimation() zur Vsync-Synchronisation.

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

    // Objekte einmal im Konstruktor erstellt
    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)
        // Nur Bereich um den Punkt neu zeichnen
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

Hardwarebeschleunigung ist standardmäßig auf Android 3.0+ (API 11+) aktiviert. Canvas-Operationen drawCircle, drawRect, drawBitmap sind hardwarebeschleunigt und werden auf der GPU ausgeführt. Allerdings sind drawTextOnPath, drawVertices, komplexe Clipping-Operationen nicht beschleunigt — verwenden Sie dafür LAYER_TYPE_SOFTWARE.

Laut Android Performance Patterns (Google, 2023) sind die Hauptursachen für Custom-View-Verzögerungen — Allokationen in onDraw (Objekterstellung pro Frame), Neuzeichnen der gesamten View bei Änderung eines Elements und fehlender LayerType.HARDWARE für statische Inhalte. Die Behebung dieser drei Probleme verbessert die FPS in den meisten Szenarien von 30 auf 60.

MethodeZweckAufruffrequenz
onDrawView-Inhalt zeichnenBei jedem invalidate()
onMeasureView-Größen bestimmenBei Layout-Änderung
onLayoutUntergeordnete Views positionierenNach onMeasure
onTouchEventBerührungsbehandlungBei jeder Berührung
onSizeChangedReaktion auf GrößenänderungBei erstem Layout und Änderung

Häufig gestellte Fragen

Wann Custom View statt Compose verwenden?

Custom View (Canvas API) ist gerechtfertigt, wenn maximale benutzerdefinierte Zeichenleistung benötigt wird (Grafiken, Videobearbeiter, Karten), Integration mit vorhandenem View-basiertem Code oder Unterstützung für Android-Versionen unter API 21. Jetpack Compose ist ein moderner Ansatz für die meisten neuen Projekte, der deklarative UI verwendet und besser für dynamische Oberflächen geeignet ist.

Muss onMeasure überschrieben werden?

Ja, wenn die View wrap_content im XML-Layout verwendet. Ohne onMeasure verhält sich wrap_content wie match_parent, da die Standardimplementierung von View.onMeasure keine Standardgröße festlegt. Wenn die View immer eine feste Größe oder match_parent hat, kann onMeasure unüberschrieben bleiben.

Wie animiert man einen Custom View?

Verwenden Sie ValueAnimator oder ObjectAnimator zum Ändern von View-Eigenschaften (Farbe, Radius, Position) und rufen Sie invalidate() im Animations-Callback zur Neuzeichnung auf. ValueAnimator läuft im Hauptthread und ist mit vsync synchronisiert. Für komplexe Animationen (Physik, Partikel) verwenden Sie Choreographer.FrameCallback oder das Android Animation Framework.

Wie behandelt man Multi-Touch im Custom View?

In onTouchEvent verwenden Sie MotionEvent.getPointerCount() zur Bestimmung der Fingeranzahl und getPointerId(i) zur Verfolgung jedes Fingers per eindeutiger ID. ACTION_POINTER_DOWN / ACTION_POINTER_UP — Ereignisse zum Hinzufügen/Entfernen von Fingern. Für Zwei-Finger-Zoom verwenden Sie ScaleGestureDetector zusammen mit onTouchEvent — es vereinfacht die Erkennung von Pinch-Gesten.

Kann Custom View in Jetpack Compose verwendet werden?

Ja, über AndroidView — eine composable-Funktion, die ein View-basiertes Element in die Compose-Hierarchie einbettet. AndroidView nimmt eine Factory zur Erstellung der View und einen update-Callback zur Zustandssynchronisation entgegen. Dies ermöglicht die Wiederverwendung vorhandener Custom Views in neuen Compose-Projekten ohne vollständiges Umschreiben.

Zusammenfassung

  • Custom View — eine benutzerdefinierte Android-Komponente, erstellt durch Vererbung von View mit Überschreiben von onDraw, onMeasure und onTouchEvent für benutzerdefiniertes Zeichnen und Verarbeitung.
  • onDraw und Canvas API — die Grundlage des Zeichnens: geometrische Formen, Text, Bitmap, Pfade — konfiguriert über Paint für Farbe, Stil und Schriftart.
  • onMeasure ist wichtig für korrektes wrap_content-Verhalten; verwendet MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) und resolveSize zur Dimensionsberechnung.
  • Benutzerdefinierte Attribute über attrs.xml und TypedArray ermöglichen die Konfiguration von Custom View aus XML-Markup mit Android-Studio-Autovervollständigung.
  • onTouchEvent mit GestureDetector bietet Berührungs-, Gesten- und Multi-Touch-Behandlung, während Hit-Testing den Interaktionsbereich bestimmt.
  • Performance: Objekte einmal im Konstruktor erstellen, invalidate(Rect) für teilweises Neuzeichnen und LayerType für komplexe Effekte verwenden.
  • Custom View integriert sich mit Jetpack Compose über AndroidView, was die Wiederverwendung fertiger Komponenten in neuen Compose-Projekten ermöglicht.

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch