Custom View i Android — ritning och händelsehantering

Författare: IT Sectr Publicerad: 2026-07-20 Lästid: 9 min

Custom View — är en anpassad gränssnittskomponent i Android, skapad genom att ärva från klassen View eller dess underklasser (Button, TextView, ImageView) och åsidosätta nyckelmetoder: onDraw för ritning, onMeasure för mätning av dimensioner och onTouchEvent för hantering av beröringar. Enligt Google Android Developer Guide (2024) används Custom View när standardkomponenterna i Android SDK inte ger önskat beteende eller utseende — till exempel för anpassade animationer, icke-standardiserade former, specialiserade diagram och spelelement.

Huvudpunkter

  • Custom View — skapas genom att ärva från View eller dess underklasser med åsidosättande av onDraw, onMeasure, onTouchEvent.
  • Ritning utförs via Canvas API — geometriska former, text, banor, Bitmap och animationer.
  • onMeasure ansvarar för korrekta dimensioner av View med hänsyn till layout-parametrar och padding.
  • Anpassade XML-attribut deklareras i res/values/attrs.xml och läses i View-konstruktorn.
  • Beröringshantering onTouchEvent gör det möjligt att lägga till stöd för gester, dra-och-släpp och multitouch.

Vad är Custom View i Android?

Custom View — är en klass som ärver android.view.View (eller en av dess underklasser) och åsidosätter systemmetoder för att implementera egen ritnings-, mätnings- och händelsehanteringslogik. Custom View är den grundläggande mekanismen i Android för att skapa unika UI-komponenter.

Android erbjuder två metoder för att skapa Custom View: arv från View (helt anpassad ritning via onDraw) och arv från en befintlig View-underklass (till exempel åsidosättande av Button eller TextView för att lägga till funktionalitet samtidigt som basbeteendet bevaras).

Grundkonstruktorn för Custom View måste acceptera Context och AttributeSet — detta gör att systemet kan skapa View från XML-kod. Om View endast används från kod räcker det med konstruktor med Context. Den tredje konstruktorn med stil behövs för stöd av Android-teman.

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

Minimal Custom View består av en konstruktor och onDraw. För korrekt funktion i Android Layout-systemet måste dock onMeasure också åsidosättas — annars kan View visas med nolldimensioner eller reagera felaktigt på layout-parametern wrap_content.

Enligt Google I/O 2023 används anpassade View i 65% av apparna från topp 100 på Google Play. De vanligaste orsakerna att skapa Custom View: anpassade animationer (35%), anpassade diagram (25%), specialiserade kontrollelement (20%) och varumärkeskomponenter (20%).

onMeasure och onLayout: hantera dimensioner

onMeasure — är metoden där View meddelar systemet sina önskade dimensioner baserat på mottaget MeasureSpec (föräldrabegränsningar). Utan korrekt implementering av onMeasure kan View ha noll höjd med wrap_content eller uppta hela skärmytan med match_parent.

MeasureSpec består av ett läge (EXACTLY, AT_MOST, UNSPECIFIED) och ett värde. EXACTLY — föräldern har angett exakt storlek (match_parent eller fast värde). AT_MOST — föräldern har angett maximum (wrap_content). UNSPECIFIED — utan begränsningar (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 — är en användbar Android-metod som förenklar implementeringen av onMeasure. Den tar emot önskad storlek och MeasureSpec och returnerar korrekt värde: för EXACTLY — exakt storlek, för AT_MOST — minimum av önskad och maximal, för UNSPECIFIED — önskad storlek.

onLayout för View (inte ViewGroup) åsidosätts vanligtvis inte — den anropas av föräldern för positionering av barn-element. För ViewGroup är onLayout obligatorisk — i den måste layout() anropas för varje barn-View.

onDraw och Canvas API: ritning

onDraw — hjärtat i Custom View. Denna metod anropas av Android vid varje omritning av View. Den får ett Canvas-objekt genom vilket all ritning utförs: geometriska former, text, banor, Bitmap och animationer.

Canvas API tillhandahåller metoder för alla grundläggande operationer: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. För att konfigurera stil används Paint — ett objekt som definierar färg, tjocklek, fyllnadsstil, typsnitt, skuggor och effekter.

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

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

    // Text
    canvas.drawText("Anpassad vy",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

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

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

Paint — är konfiguratorn för ritstil. Anti-aliasing (Paint.ANTI_ALIAS_FLAG) är obligatoriskt för jämna kanter. Subpixel text (SUBPIXEL_TEXT_FLAG) förbättrar textkvaliteten. Style.FILL, STROKE och FILL_AND_STROKE avgör om formen ska vara fylld, konturerad eller både och.

Viktigt: skapa inte objekt Paint, Path, Rect i onDraw — detta leder till allokeringar i ritningsloopen och utlöser garbage collection, vilket sänker FPS. Skapa alla ritningsobjekt i konstruktorn eller i init-metoden. För animation, använd ValueAnimator eller ObjectAnimator med invalidate() för att starta omritning.

Anpassade XML-attribut i attrs.xml

Anpassade attribut gör det möjligt att konfigurera Custom View från XML-kod precis som standard android:layout_width eller android:background. Attribut deklareras i filen res/values/attrs.xml med namn, typ och valfritt standardvärde.

Attributtyper: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (referens till resurs). För varje typ tolkar Android automatiskt värdet från XML och skickar det till 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>

I Custom View-konstruktorn läses attribut via context.obtainStyledAttributes, som returnerar en TypedArray. TypedArray tillhandahåller typade åtkomstmetoder: getColor, getDimension, getString, getBoolean, getInt. Efter läsning måste recycle() anropas på TypedArray för att frigöra resurser.

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

Användning i XML: lägg till namnområdet app (xmlns:app="http://schemas.android.com/apk/res-auto") och använd anpassade attribut som app:circleColor="@color/red". Android Studio kommer att autokomplettera och kontrollera attributtyper om deklarationen i attrs.xml är korrekt.

Beröringshantering med onTouchEvent

onTouchEvent — metoden som anropas vid varje beröring av View. Den får ett MotionEvent-objekt med information om händelsetyp (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), koordinater, tryck och antal fingrar (multitouch).

För att hantera komplexa gester (svep, skalning, lång tryckning) använd GestureDetector eller ScaleGestureDetector i kombination med onTouchEvent. GestureDetector förenklar igenkänning av onSingleTapUp, onFling, onLongPress, onDoubleTap och andra standardgester.

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 — avgöra om en beröring träffade ett specifikt område av View. För rektangulära områden använd Rect.contains(x, y). För cirklar — kontrollera avståndet från centrum: sqrt(dx^2 + dy^2) < radius. För godtyckliga former — Path.op() eller Region.contains().

Multitouch hanteras via MotionEvent.getPointerCount() och getPointerId(index). Varje finger får ett unikt ID som bevaras från ACTION_DOWN till ACTION_POINTER_UP. ACTION_MOVE kan innehålla data för alla aktiva fingrar — använd getHistoricalX/Y för interpolation av rörelse.

Prestandaoptimering av Custom View

Custom View kan bli en prestandaflaskhals om onDraw anropas ofta (animation, scroll) eller innehåller tunga operationer. Android tillhandahåller flera mekanismer för att optimera ritning av anpassade komponenter.

Layer type för komplex ritning

View.setLayerType gör det möjligt att växla renderingen av View till en programvaru- (LAYER_TYPE_SOFTWARE) eller hårdvarulager (LAYER_TYPE_HARDWARE). Software layer är användbar för komplex Canvas-grafik som inte stöds av hårdvaruacceleration — till exempel drawTextOnPath eller komplexa Path-effekter.

invalidate endast nödvändigt område

invalidate(Rect) — omritar endast det angivna området av View, inte hela komponenten. Detta är kritiskt för stora Custom View (grafik, kartor, ritdukar), där full omritning av varje bildruta orsakar FPS-fall. Använd postInvalidateOnAnimation() för synkronisering med vsync.

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

    // Objekt skapade en gång i konstruktorn
    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)
        // Omrita endast området runt punkten
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

Hårdvaruacceleration är som standard aktiverad på Android 3.0+ (API 11+). Canvas-operationerna drawCircle, drawRect, drawBitmap är hårdvaruaccelererade och körs på GPU. Dock är drawTextOnPath, drawVertices, komplexa clipping-operationer inte accelererade — för dem använd LAYER_TYPE_SOFTWARE.

Enligt Android Performance Patterns (Google, 2023) är de främsta orsakerna till lagg i Custom View: allokeringar i onDraw (skapa objekt varje bildruta), omritning av hela View när ett element ändras och avsaknad av LayerType.HARDWARE för statiskt innehåll. Att åtgärda dessa tre problem ger en FPS-ökning från 30 till 60 i de flesta scenarier.

MetodSyfteAnropsfrekvens
onDrawRita innehållet i ViewVid varje invalidate()
onMeasureBestämma dimensioner för ViewVid layoutändring
onLayoutPositionera barn-ViewEfter onMeasure
onTouchEventHantera beröringarVid varje beröring
onSizeChangedReagera på storleksändringVid första layout och ändring

Vanliga frågor

När ska man använda Custom View istället för Compose?

Custom View (Canvas API) är motiverat när maximal prestanda för anpassad ritning behövs (grafik, videoredigerare, kartor), integration med befintlig View-based kod eller stöd för Android-versioner under API 21. Jetpack Compose är det moderna tillvägagångssättet för de flesta nya projekt, med deklarativt UI och bättre lämpat för dynamiska gränssnitt.

Är det obligatoriskt att åsidosätta onMeasure?

Ja, om View använder wrap_content i XML-kod. Utan onMeasure kommer wrap_content att fungera som match_parent, eftersom standardimplementeringen av View.onMeasure inte anger en standardstorlek. Om View alltid har en fast storlek eller match_parent kan onMeasure utelämnas.

Hur animerar man en Custom View?

Använd ValueAnimator eller ObjectAnimator för att ändra View-egenskaper (färg, radie, position) och anropa invalidate() i animeringsåteruppringningen för omritning. ValueAnimator körs på huvudtråden och är synkroniserad med vsync. För komplexa animationer (fysik, partiklar) använd Choreographer.FrameCallback eller Android Animation Framework.

Hur hanterar man multitouch i Custom View?

I onTouchEvent, använd MotionEvent.getPointerCount() för att bestämma antalet fingrar och getPointerId(i) för att spåra varje finger med unikt ID. ACTION_POINTER_DOWN / ACTION_POINTER_UP — händelser för tillägg/borttagning av finger. För skalning, använd ScaleGestureDetector tillsammans med onTouchEvent — det förenklar detektering av pinch-to-zoom.

Kan Custom View användas i Jetpack Compose?

Ja, via AndroidView — en composable-funktion som infogar ett View-based element i Compose-hierarkin. AndroidView tar emot en fabrik för att skapa View och en update-återuppringning för tillståndssynkronisering. Detta möjliggör återanvändning av befintliga Custom View i nya Compose-projekt utan fullständig omskrivning.

Sammanfattning

  • Custom View — anpassad Android-komponent, skapad genom arv från View med åsidosättande av onDraw, onMeasure och onTouchEvent för anpassad ritning och hantering.
  • onDraw och Canvas API — grunden för ritning: geometriska former, text, Bitmap, banor — med konfiguration via Paint för färg, stil och typsnitt.
  • onMeasure är viktigt för korrekt funktion av wrap_content; använder MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) och resolveSize för att beräkna dimensioner.
  • Anpassade attribut via attrs.xml och TypedArray möjliggör konfiguration av Custom View från XML-kod med autocomplete-stöd i Android Studio.
  • onTouchEvent med GestureDetector ger hantering av beröringar, gester och multitouch, och hit-testing bestämmer interaktionsområdet.
  • Prestanda: skapa objekt en gång i konstruktorn, använd invalidate(Rect) för partiell omritning och LayerType för komplexa effekter.
  • Custom View integreras med Jetpack Compose via AndroidView, vilket möjliggör återanvändning av färdiga komponenter i nya Compose-projekt.

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också