onDraw() — co to jest, mechanizm wywołania i nadpisywanie metody

Autor: IT Sectr Opublikowano: 2026-07-22 Czas czytania: 10 min

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) — metoda View wywoływana przez system do renderowania zawartości na Canvas
  • invalidate() — żądanie przerysowania View, prowadzące do wywołania onDraw w następnym cyklu klatki
  • Canvas API udostępnia metody drawCircle, drawBitmap, drawText, drawPath i inne do rysowania
  • System Android wykonuje renderowanie w wątku UI, dlatego onDraw powinien być lekki i szybki
  • Hardware acceleration automatycznie przyspiesza renderowanie przez GPU, począwszy od Android 3.0 (API 11)

Co to jest onDraw()?

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ą.

Cykl renderowania View

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.

Faza draw: dispatchDraw i onDraw

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

Kiedy system wywołuje onDraw

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

Przyspieszenie sprzętowe

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 API do niestandardowej grafiki

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 CanvasPrzeznaczenieKluczowe parametry Paint
drawCircleRysowanie kołacenterX, centerY, radius, paint
drawRectRysowanie prostokątaleft, top, right, bottom, paint
drawLineRysowanie liniistartX, startY, endX, endY, paint
drawPathRysowanie dowolnego konturupath, paint
drawBitmapRysowanie obrazubitmap, srcRect, dstRect, paint
drawTextRysowanie tekstutext, x, y, paint
drawArcRysowanie łuku lub sektoraovalRect, startAngle, sweepAngle, useCenter, paint
drawOvalRysowanie owaluovalRect, paint

Obiekt 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.

Współrzędne i transformacje

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.

Wydajność onDraw

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.

Zasady wydajnego onDraw

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.

Używanie clipRect do optymalizacji

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.

Layer — zapisywanie i przywracanie

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 i postInvalidate

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.

Przykłady kodu w Kotlin

Rozważmy praktyczne przykłady nadpisywania onDraw do tworzenia niestandardowych widoków w Android na Kotlin.

Niestandardowy View — okrągły pasek postępu

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.

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

Niestandardowy View — gradientowe tło z tekstem

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.

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

Animowany zegar z użyciem invalidate

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ą.

kotlin
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

Czy można wywoływać onDraw bezpośrednio?

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.

Dlaczego onDraw nie jest wywoływany po invalidate?

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.

Jak często może być wywoływany onDraw?

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.

Jaka jest różnica między onDraw a dispatchDraw?

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.

Czy można używać Canvas poza onDraw?

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

  • onDraw(Canvas) — kluczowa metoda View do niestandardowego renderowania z użyciem Canvas API w Android
  • Cykl renderowania składa się z faz measure, layout i draw — onDraw jest wywoływany w fazie draw
  • Canvas API zawiera metody drawCircle, drawRect, drawPath, drawBitmap, drawText i inne
  • invalidate() żąda przerysowania z wątku UI, postInvalidate() — z wątku tła
  • Częściowe przerysowanie przez invalidate(Rect) optymalizuje renderowanie, aktualizując tylko dirty-obszar
  • Wydajność onDraw jest krytyczna: tworzenie obiektów wewnątrz metody powoduje GC i pomijanie klatek
  • Przyspieszenie sprzętowe (od API 11) automatycznie przyspiesza operacje Canvas przez GPU

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.

Omów projekt

Przeczytaj również