onDraw() — to kluczowa metoda klasy View w Android SDK, odpowiedzialna za renderowanie zawartości widoku. System wywołuje onDraw przy każdej aktualizacji wyglądu View — podczas początkowego renderowania, po invalidate() lub przy zmianie rozmiarów. Według Android Developers Documentation (2026), nadpisywanie onDraw z użyciem Canvas API jest podstawowym sposobem tworzenia niestandardowej grafiki w aplikacjach Android.
Najważniejsze
onDraw(Canvas canvas) — to chroniona metoda klasy android.view.View, wywoływana przez system Android, gdy widok musi zostać renderowany. Deweloper nadpisuje tę metodę, aby zaimplementować niestandardowe renderowanie: kształty, tekst, obrazy, gradienty, animacje. Parametr Canvas to powierzchnia graficzna, na której wykonywane są wszystkie operacje rysowania.
Sygnatura metody jest prosta: override fun onDraw(canvas: Canvas). Wewnątrz tej metody nie można tworzyć nowych obiektów (aby uniknąć obciążenia GC), wykonywać długich operacji ani pracować z siecią. View zawiera również metodę onDraw w swoim cyklu życia pomiędzy onLayout (obliczanie pozycji) a dispatchDraw (renderowanie elementów potomnych).
Przy dziedziczeniu z View nadpisanie onDraw jest obowiązkowe do wyświetlenia jakiejkolwiek zawartości — w przeciwnym razie View będzie puste. Przy dziedziczeniu z istniejących podklas (TextView, ImageView, Button) nadpisanie onDraw jest opcjonalne i używane do dodawania dodatkowej grafiki ponad standardową zawartością.
Android wykonuje renderowanie View w ściśle określonej kolejności, wywoływanej z głównego wątku (UI thread). Pełny cykl składa się z trzech faz: measure (pomiar rozmiarów), layout (obliczanie pozycji) i draw (renderowanie). onDraw jest częścią fazy draw i jest wywoływany po tym, jak rozmiary i położenie View zostały już określone.
draw(Canvas canvas) — to publiczna metoda View, która wywołuje onDraw i dispatchDraw. Metoda draw nie jest przeznaczona do nadpisywania — zamiast niej nadpisywany jest onDraw. Po wykonaniu onDraw dla bieżącego View wywoływany jest dispatchDraw, który rekurencyjnie wywołuje draw dla wszystkich widoków potomnych (jeśli bieżący View jest kontenerem ViewGroup).
onDraw jest wywoływany przy następujących warunkach: 1) View po raz pierwszy staje się widoczny (po setVisibility(VISIBLE)); 2) aplikacja wywołuje invalidate() dla tego View lub jego rodzica; 3) zmienia się rozmiar View (nowy layout); 4) zmienia się animacja związana z View (przez ValueAnimator lub ObjectAnimator); 5) zmienia się stan View (pressed, focused, enabled).
Od Android 3.0 (API 11) większość operacji Canvas jest wykonywana z przyspieszeniem sprzętowym przez GPU. Deweloper może sprawdzić, czy przyspieszenie jest włączone, przez canvas.isHardwareAccelerated(). Przy przyspieszeniu sprzętowym niektóre operacje Canvas nie są obsługiwane (np. clipPath z obszarem nieprostokątnym) i mogą być ignorowane lub powodować błędy.
Canvas udostępnia bogaty zestaw metod do rysowania. Wszystkie metody działają w bieżącym układzie współrzędnych, który może być zmieniony przez transformacje (translate, rotate, scale, skew). Każda metoda przyjmuje obiekt Paint, który określa kolor, styl, grubość linii, efekty i wygładzanie.
| Metoda Canvas | Przeznaczenie | Kluczowe parametry Paint |
|---|---|---|
| drawCircle | Rysowanie koła | centerX, centerY, radius, paint |
| drawRect | Rysowanie prostokąta | left, top, right, bottom, paint |
| drawLine | Rysowanie linii | startX, startY, endX, endY, paint |
| drawPath | Rysowanie dowolnego konturu | path, paint |
| drawBitmap | Rysowanie obrazu | bitmap, srcRect, dstRect, paint |
| drawText | Rysowanie tekstu | text, x, y, paint |
| drawArc | Rysowanie łuku lub sektora | ovalRect, startAngle, sweepAngle, useCenter, paint |
| drawOval | Rysowanie owalu | ovalRect, paint |
Paint określa styl renderowania: kolor (setColor), grubość linii (setStrokeWidth), styl wypełnienia (setStyle — FILL, STROKE, FILL_AND_STROKE), wygładzanie (setAntiAlias), efekty (setShadowLayer, setMaskFilter) i gradienty (setShader). Zaleca się tworzenie i konfigurowanie Paint raz w konstruktorze View, a nie wewnątrz onDraw, aby uniknąć tworzenia obiektów w cyklu renderowania.
Canvas używa układu współrzędnych, w którym oś X skierowana jest w prawo, a oś Y w dół. Początek układu (0, 0) to lewy górny róg View. Przed wywołaniem metod draw deweloper może zmienić układ współrzędnych przez canvas.translate(dx, dy), canvas.rotate(degrees, px, py), canvas.scale(sx, sy, px, py). Wszystkie kolejne operacje rysowania będą wykonywane w przekształconym układzie.
onDraw jest wykonywany w głównym wątku UI, a jego czas trwania bezpośrednio wpływa na liczbę klatek na sekundę (fps). Aby utrzymać 60 fps, metoda onDraw musi zakończyć się w ciągu nie więcej niż 16 milisekund. Każde spowolnienie prowadzi do pomijania klatek (jank), co wizualnie odbierane jest jako zacinanie się animacji lub opóźnienie podczas przewijania.
Podstawowe zasady optymalizacji onDraw obejmują: 1) nie tworzyć obiektów wewnątrz onDraw — wszystkie Paint, Path, Rect powinny być polami klasy; 2) nie alokować pamięci (new) — powoduje to uruchomienie garbage collectora (GC) i pomijanie klatek; 3) buforować złożone operacje w Bitmap przez Bitmap.createBitmap i rysować gotową Bitmap; 4) nie wykonywać operacji wejścia-wyjścia ani ładowania zasobów.
clipRect ogranicza obszar rysowania do określonego prostokąta. Służy do tego, aby Canvas nie tracił czasu na renderowanie pikseli poza widocznym obszarem. Szczególnie skuteczny przy przewijaniu dużych list lub skalowaniu: ustaw clipRect na widoczny obszar (dirty rect), a Canvas będzie rysować tylko to, co jest widoczne.
canvas.save() zapisuje bieżący stan Canvas (transformację i clip) na stosie. canvas.restore() przywraca ostatni zapisany stan. Jest to niezbędne do tymczasowych transformacji: zapisujesz stan, stosujesz translate/rotate, rysujesz, następnie przywracasz. save i restore są lekkie i nie wpływają na wydajność przy rozsądnym użyciu (mniej niż 10–15 poziomów zagnieżdżenia).
invalidate() — to metoda View, która informuje system, że zawartość View jest nieaktualna i wymaga przerysowania. Wywołanie invalidate inicjuje nowy cykl draw, w którym zostanie wywołany onDraw. invalidate powinien być wywoływany tylko z wątku UI (main thread). Do wywołania z wątku tła używany jest postInvalidate.
postInvalidate() — bezpieczna wątkowo wersja invalidate, która wysyła żądanie przerysowania do kolejki głównego wątku przez Handler. Używana w zadaniach tła, gdy trzeba zaktualizować View po zakończeniu ładowania danych lub obliczeń. Łączy się z runOnUiThread do synchronizacji z UI.
Do częściowego przerysowania używane jest invalidate(Rect dirty) lub invalidate(int l, int t, int r, int b). To wskazuje systemowi, że zmienił się tylko określony obszar, a nie cały View. Android optymalizuje renderowanie, przerysowując tylko dirty rect, co znacznie zwiększa wydajność przy animacjach z częściowymi aktualizacjami.
Rozważmy praktyczne przykłady nadpisywania onDraw do tworzenia niestandardowych widoków w Android na Kotlin.
Przykład tworzy okrągły wskaźnik postępu z animowanym wypełnieniem. onDraw rysuje dwa koła: szare tło i kolorowe wypełnienie, którego łuk jest określany przez bieżący postęp. Wywołanie invalidate w setProgress zapewnia przerysowanie przy zmianie wartości.
class CircularProgressView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val bgPaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 12f
color = Color.parseColor("#E0E0E0")
isAntiAlias = true
}
private val progressPaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 12f
color = Color.BLUE
isAntiAlias = true
strokeCap = Paint.Cap.ROUND
}
var progress: Float = 0f
set(value) {
field = value.coerceIn(0f, 1f)
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val size = minOf(width, height) - 24f
val oval = RectF(12f, 12f,
12f + size, 12f + size)
canvas.drawArc(oval, 270f, 360f, false, bgPaint)
canvas.drawArc(oval, 270f,
progress * 360f,
false, progressPaint)
}
}
Przykład rysuje prostokąt z gradientem i tekstem na nim. Gradient jest tworzony przez LinearGradient (Shader), a tekst przez drawText. Oba Paint są tworzone raz w konstruktorze, a w onDraw zmieniany jest tylko rozmiar prostokąta w zależności od bieżącej szerokości View.
class GradientTextView(context: Context)
: View(context) {
private val shaderPaint = Paint().apply {
isAntiAlias = true
}
private val textPaint = Paint().apply {
color = Color.WHITE
textSize = 48f
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val gradient = LinearGradient(
0f, 0f, width.toFloat(), 0f,
Color.parseColor("#FF6B35"),
Color.parseColor("#FF0040"),
Shader.TileMode.CLAMP
)
shaderPaint.shader = gradient
canvas.drawRect(0f, 0f,
width.toFloat(),
height.toFloat(), shaderPaint)
val xCenter = width / 2f
val yCenter = height / 2f
val yOffset = (textPaint.descent() +
textPaint.ascent()) / 2f
canvas.drawText("Witaj z Canvas",
xCenter, yCenter - yOffset,
textPaint)
}
}
Przykład tworzy animowany zegar, aktualizujący renderowanie co 1000 milisekund. postDelayed w onDraw wywołuje invalidate po sekundzie, tworząc nieskończony cykl przerysowania. Wskazówki zegara są rysowane przez drawLine z różną długością i grubością.
class AnalogClockView(context: Context)
: View(context) {
private val handPaint = Paint().apply {
style = Paint.Style.STROKE
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
val radius = minOf(cx, cy) - 20f
val now = Calendar.getInstance()
val hours = now.get(Calendar.HOUR) % 12
val minutes = now.get(Calendar.MINUTE)
val seconds = now.get(Calendar.SECOND)
val secondAngle = Math.toRadians(
seconds * 6f - 90f)
handPaint.strokeWidth = 2f
handPaint.color = Color.RED
canvas.drawLine(cx, cy,
cx + cos(secondAngle) * radius * 0.8f,
cy + sin(secondAngle) * radius * 0.8f,
handPaint)
postDelayed({ invalidate() }, 1000)
}
}
Często zadawane pytania
Nie, nie należy wywoływać onDraw bezpośrednio. Do żądania przerysowania używaj invalidate() (z wątku UI) lub postInvalidate() (z wątku tła). System sam zdecyduje, kiedy wywołać onDraw, i optymalizuje renderowanie, łącząc wielokrotne invalidate w jeden przebieg.
Przyczyny: View jest ukryty (setVisibility(GONE) lub INVISIBLE); View nie został dodany do hierarchii Window; rozmiar View wynosi 0 (width lub height = 0); wywoływane jest setWillNotDraw(true) (domyślnie true dla ViewGroup). Sprawdź każdy z tych punktów podczas debugowania.
Przy 60 fps onDraw może być wywoływany do 60 razy na sekundę. Przy 120 fps (na urządzeniach z wyświetlaczami 120 Hz) — do 120 razy. Każde wywołanie powinno mieścić się w ~8–16 milisekundach. Przy aktywnych animacjach przez ObjectAnimator lub ValueAnimator częstotliwość jest określana przez liczbę klatek animacji.
onDraw odpowiada za renderowanie zawartości samego View. dispatchDraw odpowiada za renderowanie widoków potomnych (w ViewGroup). dispatchDraw jest wywoływany po onDraw i rekurencyjnie przechodzi przez wszystkie elementy potomne. Dla zwykłego View (nie ViewGroup) dispatchDraw jest pusty.
Tak, Canvas można tworzyć dla Bitmap przez Canvas(bitmap) i rysować na nim w dowolnym wątku. Jest to używane do wstępnego obliczania złożonej grafiki w tle. Jednak Canvas przekazywany w onDraw należy do systemu i może być używany tylko w wątku UI wewnątrz metody onDraw.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również