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 — ä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.
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 — ä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).
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 — 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.
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 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.
<!-- 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.
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.
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.
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.
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.
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(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.
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.
| Metod | Syfte | Anropsfrekvens |
|---|---|---|
| onDraw | Rita innehållet i View | Vid varje invalidate() |
| onMeasure | Bestämma dimensioner för View | Vid layoutändring |
| onLayout | Positionera barn-View | Efter onMeasure |
| onTouchEvent | Hantera beröringar | Vid varje beröring |
| onSizeChanged | Reagera på storleksändring | Vid första layout och ändring |
Vanliga frågor
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.
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.
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.
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.
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
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.
Läs också