Custom View — je vlastní komponenta rozhraní v Androidu, vytvořená děděním z třídy View nebo jejích podtříd (Button, TextView, ImageView) a přepsáním klíčových metod: onDraw pro kreslení, onMeasure pro měření rozměrů a onTouchEvent pro zpracování dotyků. Podle Google Android Developer Guide (2024) se Custom View používá, když standardní komponenty Android SDK neposkytují požadované chování nebo vzhled — například pro vlastní animace, nestandardní tvary, specializované grafy a herní prvky.
Hlavní body
Custom View — je třída, která dědí z android.view.View (nebo jedné z jeho podtříd) a přepisuje systémové metody pro implementaci vlastní logiky kreslení, měření a zpracování událostí. Custom View je základní mechanismus Androidu pro vytváření jedinečných UI komponent.
Android nabízí dva přístupy k vytvoření Custom View: dědění z View (zcela vlastní kreslení přes onDraw) a dědění z existující podtřídy View (například přepsání Button nebo TextView pro přidání funkcionality při zachování základního chování).
Základní konstruktor Custom View musí přijímat Context a AttributeSet — to umožňuje systému vytvářet View z XML kódu. Pokud bude View použito pouze z kódu, stačí konstruktor s Context. Třetí konstruktor se stylem je potřebný pro podporu Android témat.
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)
}
}
Minimální Custom View se skládá z konstruktoru a onDraw. Pro správnou funkci v systému Layout Android je však také nutné přepsat onMeasure — jinak se View může zobrazit s nulovými rozměry nebo nesprávně reagovat na parametry layout wrap_content.
Podle Google I/O 2023 se vlastní View používají v 65% aplikací z top 100 Google Play. Nejčastější důvody vytvoření Custom View: vlastní animace (35%), vlastní grafy a diagramy (25%), specializované ovládací prvky (20%) a značkové komponenty (20%).
onMeasure — je metoda, ve které View sděluje systému své požadované rozměry na základě předaného MeasureSpec (rodičovská omezení). Bez správné implementace onMeasure může mít View nulovou výšku při wrap_content nebo zabírat celou plochu obrazovky při match_parent.
MeasureSpec se skládá z režimu (EXACTLY, AT_MOST, UNSPECIFIED) a hodnoty. EXACTLY — rodič nastavil přesný rozměr (match_parent nebo pevná hodnota). AT_MOST — rodič nastavil maximum (wrap_content). UNSPECIFIED — bez omezení (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 — je užitečná metoda Androidu, která zjednodušuje implementaci onMeasure. Přijímá požadovaný rozměr a MeasureSpec a vrací správnou hodnotu: pro EXACTLY — přesný rozměr, pro AT_MOST — minimum z požadovaného a maximálního, pro UNSPECIFIED — požadovaný rozměr.
onLayout pro View (ne ViewGroup) se obvykle nepřepisuje — je volán rodičem pro pozicování potomků. Pro ViewGroup je onLayout povinný — v něm je třeba zavolat layout() pro každé potomní View.
onDraw — srdce Custom View. Tato metoda je volána Androidem při každém překreslení View. Dostává objekt Canvas, přes který se provádí veškeré kreslení: geometrické tvary, text, cesty, Bitmap a animace.
Canvas API poskytuje metody pro všechny základní operace: drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Pro konfiguraci stylu se používá Paint — objekt definující barvu, tloušťku, styl výplně, písmo, stíny a efekty.
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Pozadí
canvas.drawRect(0f, 0f,
width.toFloat(), height.toFloat(), bgPaint)
// Text
canvas.drawText("Vlastní zobrazení",
paddingLeft.toFloat(),
paddingTop.toFloat() + textPaint.textSize,
textPaint)
// Kruh
canvas.drawCircle(
width / 2f,
height / 2f,
radius, circlePaint)
// Cesta (Bezierova křivka)
val path = Path().apply {
moveTo(0f, height.toFloat())
quadTo(width / 2f, 0f,
width.toFloat(), height.toFloat())
}
canvas.drawPath(path, pathPaint)
}
Paint — je konfigurátor stylu kreslení. Anti-aliasing (Paint.ANTI_ALIAS_FLAG) je povinný pro hladké okraje. Subpixel text (SUBPIXEL_TEXT_FLAG) zlepšuje kvalitu textu. Style.FILL, STROKE a FILL_AND_STROKE určují, zda bude tvar vyplněný, obtažený obrysem nebo obojí.
Důležité: nevytvářejte objekty Paint, Path, Rect v onDraw — to vede k alokacím v cyklu kreslení a spouští garbage collection, což snižuje FPS. Vytvářejte všechny kreslicí objekty v konstruktoru nebo v metodě init. Pro animaci použijte ValueAnimator nebo ObjectAnimator s invalidate() pro spuštění překreslení.
Vlastní atributy umožňují konfigurovat Custom View z XML kódu stejně jako standardní android:layout_width nebo android:background. Atributy se deklarují v souboru res/values/attrs.xml s uvedením názvu, typu a volitelné výchozí hodnoty.
Typy atributů: string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (odkaz na zdroj). Pro každý typ Android automaticky parsuje hodnotu z XML a předává ji do 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>
V konstruktoru Custom View se atributy čtou přes context.obtainStyledAttributes, který vrací TypedArray. TypedArray poskytuje typované přístupové metody: getColor, getDimension, getString, getBoolean, getInt. Po přečtení je třeba zavolat recycle() na TypedArray pro uvolnění zdrojů.
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()
}
Použití v XML: přidejte jmenný prostor app (xmlns:app="http://schemas.android.com/apk/res-auto") a používejte vlastní atributy jako app:circleColor="@color/red". Android Studio bude automaticky doplňovat a kontrolovat typy atributů, pokud je deklarace v attrs.xml správná.
onTouchEvent — metoda volaná při každém dotyku View. Dostává objekt MotionEvent s informací o typu události (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), souřadnicích, tlaku a počtu prstů (multitouch).
Pro zpracování složitých gest (swipe, škálování, dlouhé stisknutí) použijte GestureDetector nebo ScaleGestureDetector v kombinaci s onTouchEvent. GestureDetector zjednodušuje rozpoznávání onSingleTapUp, onFling, onLongPress, onDoubleTap a dalších standardních gest.
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 — určení, zda dotyk zasáhl konkrétní oblast View. Pro obdélníkové oblasti použijte Rect.contains(x, y). Pro kruhy — kontrola vzdálenosti od středu: sqrt(dx^2 + dy^2) < radius. Pro libovolné tvary — Path.op() nebo Region.contains().
Multitouch se zpracovává přes MotionEvent.getPointerCount() a getPointerId(index). Každý prst dostává jedinečné ID, které je zachováno od ACTION_DOWN do ACTION_POINTER_UP. ACTION_MOVE může obsahovat data všech aktivních prstů — použijte getHistoricalX/Y pro interpolaci pohybu.
Custom View se může stát úzkým hrdlem výkonu, pokud je onDraw volán často (animace, scroll) nebo obsahuje těžké operace. Android poskytuje několik mechanismů pro optimalizaci kreslení vlastních komponent.
View.setLayerType umožňuje přepnout renderování View na softwarovou (LAYER_TYPE_SOFTWARE) nebo hardwarovou (LAYER_TYPE_HARDWARE) vrstvu. Software layer je užitečný pro složitou Canvas grafiku, která není podporována hardwarovou akcelerací — například drawTextOnPath nebo složité Path efekty.
invalidate(Rect) — překresluje pouze zadanou oblast View, nikoli celou komponentu. To je kriticky důležité pro velké Custom View (grafika, mapy, kreslicí plátna), kde úplné překreslení každého snímku způsobuje pokles FPS. Pro synchronizaci s vsync použijte postInvalidateOnAnimation().
class EfficientCustomView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
// Objekty vytvořené jednou v konstruktoru
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)
// Překreslit pouze oblast kolem bodu
rect.set(
(x - 10).toInt(),
(y - 10).toInt(),
(x + 10).toInt(),
(y + 10).toInt()
)
invalidate(rect)
}
}
Hardwarová akcelerace je ve výchozím nastavení zapnuta na Android 3.0+ (API 11+). Canvas operace drawCircle, drawRect, drawBitmap jsou hardwarově akcelerovány a prováděny na GPU. Nicméně drawTextOnPath, drawVertices, složité clipping operace nejsou akcelerovány — pro ně použijte LAYER_TYPE_SOFTWARE.
Podle Android Performance Patterns (Google, 2023) jsou hlavními příčinami lagů Custom View alokace v onDraw (vytváření objektů v každém snímku), překreslování celého View při změně jednoho prvku a absence LayerType.HARDWARE pro statický obsah. Oprava těchto tří problémů přináší zvýšení FPS z 30 na 60 ve většině scénářů.
| Metoda | Účel | Frekvence volání|
|---|---|---|
| onDraw | Kreslení obsahu View | Při každém invalidate() |
| onMeasure | Určení rozměrů View | Při změně layoutu |
| onLayout | Pozicování potomních View | Po onMeasure |
| onTouchEvent | Zpracování dotyků | Při každém dotyku |
| onSizeChanged | Reakce na změnu velikosti | Při prvním layout a změně |
Často kladené otázky
Custom View (Canvas API) je opodstatněný, když je potřeba maximální výkon vlastního kreslení (grafika, video editory, mapy), integrace s existujícím View-based kódem nebo podpora Android verzí pod API 21. Jetpack Compose je moderní přístup pro většinu nových projektů, používající deklarativní UI a lépe se hodí pro dynamická rozhraní.
Ano, pokud View používá wrap_content v XML kódu. Bez onMeasure bude wrap_content fungovat jako match_parent, protože standardní implementace View.onMeasure nenastavuje výchozí velikost. Pokud má View vždy pevnou velikost nebo match_parent, onMeasure lze přeskočit.
Použijte ValueAnimator nebo ObjectAnimator pro změnu vlastností View (barva, poloměr, pozice) a volejte invalidate() v callbacku animace pro překreslení. ValueAnimator pracuje na hlavním vlákně a je synchronizován s vsync. Pro složité animace (fyzika, částice) použijte Choreographer.FrameCallback nebo Android Animation Framework.
V onTouchEvent použijte MotionEvent.getPointerCount() pro určení počtu prstů a getPointerId(i) pro sledování každého prstu podle jedinečného ID. ACTION_POINTER_DOWN / ACTION_POINTER_UP — události přidání/odebrání prstu. Pro škálování použijte ScaleGestureDetector v páru s onTouchEvent — zjednodušuje detekci pinch-to-zoom.
Ano, přes AndroidView — composable funkci, která vkládá View-based prvek do Compose hierarchie. AndroidView přijímá továrnu pro vytvoření View a callback update pro synchronizaci stavu. To umožňuje znovupoužití stávajících Custom View v nových Compose projektech bez úplného přepisování.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také