CustomPainter — co to jest, metody paint i shouldRepaint we Flutter

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

CustomPainter — to abstrakcyjna klasa z Flutter SDK, zapewniająca programistom pełną kontrolę nad renderowaniem niestandardowej grafiki 2D na Canvas. Programista tworzy podklasę CustomPainter i implementuje dwie obowiązkowe metody: paint(Canvas, Size) i shouldRepaint(covariant CustomPainter oldDelegate). Według Flutter API Documentation (2026), CustomPainter jest używany razem z widgetem CustomPaint, który deleguje renderowanie do przekazanego obiektu CustomPainter na każdej klatce animacji lub zmiany stanu.

Najważniejsze

  • CustomPainter — abstrakcyjna klasa Flutter, definiująca niestandardowe renderowanie na Canvas przez metody paint i shouldRepaint
  • CustomPaint — widget, przyjmujący CustomPainter jako parametr painter lub foregroundPainter i wyświetlający wynik na ekranie
  • Canvas — obiekt udostępniający metody rysowania (drawLine, drawCircle, drawPath, drawRect, drawArc i inne)
  • shouldRepaint — metoda optymalizacji, określająca, czy obiekt wymaga przerysowania przy aktualizacji
  • size — rozmiar obszaru rysowania, przekazywany do paint(Canvas canvas, Size size) z nadrzędnego widgetu

Co to jest CustomPainter?

CustomPainter — to podstawowa klasa Flutter do implementacji dowolnej grafiki 2D. Zastępuje ona podejście z nadpisywaniem onDraw w Android, zapewniając bardziej elastyczny i deklaratywny model renderowania. Programista tworzy podklasę CustomPainter, nadpisuje metodę paint, gdzie otrzymuje Canvas i Size, i wykonuje na Canvas wszystkie operacje rysowania. Widget CustomPaint zarządza wywołaniem paint i integruje wynik z drzewem widgetów.

Kluczowa różnica między CustomPainter a bezpośrednim rysowaniem przez Canvas — oddzielenie logiki renderowania (paint) i wyzwalacza przerysowania (shouldRepaint). System Flutter wywołuje shouldRepaint za każdym razem, gdy nadrzędny widget się zmienia. Jeśli shouldRepaint zwraca false, paint nie jest wywoływany, co oszczędza zasoby. Ten mechanizm jest analogiczny do shouldRebuild w RenderObject i jest częścią architektury Flutter z jej trzema drzewami (Widget, Element, RenderObject).

CustomPaint — to widget, który przyjmuje parametry painter (warstwa tła) i foregroundPainter (warstwa przednia). Oba parametry przyjmują obiekty CustomPainter. Malarz tła rysuje przed dzieckiem CustomPaint, a foregroundPainter — po. Pozwala to używać CustomPaint jako kontenera z dekoracyjnym tłem lub nakładką, nie tracąc możliwości osadzania zwykłych widgetów w środku.

Canvas i Paint we Flutter

Canvas — to centralny obiekt do rysowania we Flutter, hermetyzujący powierzchnię graficzną. Udostępnia około 40 metod rysowania: drawLine, drawCircle, drawRect, drawPath, drawArc, drawImage, drawPicture, drawPoints i inne. Canvas we Flutter odpowiada analogicznej klasie w Android Canvas, ale jest dostosowany do architektury Dart i Flutter Engine. Canvas działa w układzie współrzędnych, gdzie początek (0, 0) to lewy górny róg.

Paint — nie mylić z metodą paint() — to klasa określająca styl rysowania: kolor (color), grubość linii (strokeWidth), styl (style: fill lub stroke), przezroczystość, tryb mieszania (blendMode) i maskę wygładzania. Paint jest konfigurowany przed rysowaniem i przekazywany do metod Canvas. Ważne: Paint to zmienny obiekt, ale zaleca się utworzenie go raz i ponowne użycie, zmieniając tylko niezbędne parametry.

Metoda CanvasPrzeznaczenieParametry
drawCircleRysowanie okręguOffset center, double radius, Paint paint
drawLineRysowanie odcinkaOffset p1, Offset p2, Paint paint
drawRectRysowanie prostokątaRect rect, Paint paint
drawPathRysowanie dowolnego konturuPath path, Paint paint
drawArcRysowanie łuku lub wycinkaRect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint
drawRRectProstokąt z zaokrąglonymi narożnikamiRRect rrect, Paint paint
drawOvalRysowanie owaluRect rect, Paint paint
drawImageRysowanie obrazu z ImageImage image, Offset p, Paint paint

Transformacje Canvas

Canvas obsługuje transformacje układu współrzędnych: translate(double dx, double dy) przesuwa początek współrzędnych; rotate(double radians) obraca układ; scale(double sx, double sy) skaluje; skew(double sx, double sy) pochyla. Przed transformacją zaleca się zapisanie stanu Canvas przez save(), a po zakończeniu przywrócenie przez restore(). Zapobiega to kumulacji transformacji w kolejnych wywołaniach paint.

Metody paint i shouldRepaint

paint(Canvas canvas, Size size) — obowiązkowa metoda, w której implementowana jest cała logika rysowania. Parametr canvas — powierzchnia graficzna do rysowania. Parametr size — rozmiar obszaru przeznaczonego do rysowania (szerokość i wysokość w pikselach logicznych). Canvas jest wstępnie skonfigurowany tak, że początek współrzędnych (0,0) odpowiada lewemu górnemu rogowi obszaru rysowania. Wszystkie współrzędne są określane względem tego początku.

shouldRepaint(covariant CustomPainter oldDelegate) — obowiązkowa metoda optymalizująca częstotliwość przerysowania. Flutter wywołuje shouldRepaint przy każdej aktualizacji drzewa widgetów, która dotyczy CustomPainter. Jeśli metoda zwraca false, już narysowany obraz jest uznawany za ważny i paint nie jest wywoływany. Typowa implementacja: return oldDelegate.someField != someField — porównanie pól starego i nowego malarza w celu określenia potrzeby przerysowania.

Dla animowanych lub stale zmieniających się obrazów (zegary, wskaźniki postępu, wykresy) shouldRepaint powinien zwracać true przy każdej zmianie stanu. Jeśli obiekt jest statyczny (logo, ikona, tło), shouldRepaint zwraca false po pierwszym renderowaniu. Flutter automatycznie buforuje wynik paint i przerysowuje tylko przy zmianie rozmiaru, przezroczystości lub wywołaniu shouldRepaint.

Przykład niestandardowego rysowania z CustomPainter w Dart

Stwórzmy GaugePainter — niestandardowy okrągły wskaźnik z animowanym wypełnieniem. CustomPainter przyjmuje bieżącą wartość (progress od 0.0 do 1.0) i rysuje łuk z gradientem. W metodzie shouldRepaint porównywana jest wartość progress w celu optymalizacji. CustomPaint jest umieszczany w drzewie widgetów z parametrem size w celu ograniczenia obszaru rysowania.

dart
class GaugePainter extends CustomPainter {
    final double progress;
    final Color startColor;
    final Color endColor;

    GaugePainter({
        required this.progress,
        this.startColor = Colors.blue,
        this.endColor = Colors.cyan,
    });

    @override
    void paint(Canvas canvas, Size size) {
        final center = Offset(size.width / 2,
            size.height / 2);
        final radius = center.dx - 12;
        final bgPaint = Paint()
            ..color = Colors.grey.withOpacity(0.3)
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        final progressPaint = Paint()
            ..shader = LinearGradient(
                colors: [startColor, endColor],
            ).createShader(
                Rect.fromCircle(
                    center, radius))
            ..style = PaintingStyle.stroke
            ..strokeWidth = 16
            ..strokeCap = StrokeCap.round;

        canvas.drawCircle(center, radius, bgPaint);
        canvas.drawArc(
            Rect.fromCircle(center, radius),
            -math.pi / 2,
            2 * math.pi * progress,
            false, progressPaint);
    }

    @override
    bool shouldRepaint(
        GaugePainter oldDelegate) {
        return oldDelegate.progress != progress;
    }
}

CustomPainter z Path dla złożonych figur

Przykład rysuje wykres falowy przy użyciu Path. Path pozwala budować dowolne kontury z linii, krzywych Beziera i łuków. Po zbudowaniu kontur jest wypełniany kolorem lub obrysowywany linią. W przykładzie wykres jest budowany na podstawie tablicy punktów imitujących kształt fali.

dart
class WavePainter extends CustomPainter {
    final List<double> values;

    WavePainter({required this.values});

    @override
    void paint(Canvas canvas, Size size) {
        final path = Path();
        final dx = size.width /
            (values.length - 1);

        path.moveTo(0, size.height);
        for (var i = 0; i < values.length; i++) {
            final x = i * dx;
            final y = size.height *
                (1 - values[i]);
            path.lineTo(x, y);
        }
        path.lineTo(size.width, size.height);
        path.close();

        final fillPaint = Paint()
            ..color = Colors.blue.withOpacity(0.3)
            ..style = PaintingStyle.fill;

        canvas.drawPath(path, fillPaint);
    }

    @override
    bool shouldRepaint(
        WavePainter oldDelegate) {
        return oldDelegate.values != values;
    }
}

ForegroundPainter i backgroundPainter: różnica

painter (tła) rysuje przed dzieckiem CustomPaint. Jeśli CustomPaint ma dziecko (parametr child), malarz tła jest wyświetlany pod nim. To nadaje się do dekoracji: paski postępu, wzory tła, znaki wodne. foregroundPainter rysuje po dziecku CustomPaint, czyli nad nim. Jest używany do nakładek: refleksów, masek, linijek pomiarowych, adnotacji.

Różnica między painter a foregroundPainter jest kluczowa dla Z-order. Jeśli trzeba narysować coś za zawartością — użyj painter. Jeśli nad zawartością — foregroundPainter. W niektórych przypadkach można użyć obu malarzy jednocześnie: tło przez painter, nakładkę przez foregroundPainter. Pozwala to oddzielić logikę różnych warstw graficznych na dwie oddzielne klasy CustomPainter.

Parametr size w metodzie paint jest taki sam dla obu malarzy i jest określany przez rozmiar obszaru przeznaczonego pod CustomPaint. Rozmiar można ustawić jawnie przez parametr size w CustomPaint lub niejawnie przez nadrzędny kontener. Jeśli size jest równy Size.zero, paint nie jest wywoływany — Flutter pomija renderowanie dla zerowego obszaru. To zachowanie należy uwzględnić przy animowanym pojawianiu się elementu.

Optymalizacja wydajności CustomPainter

Główna zasada wydajności CustomPainter — nie tworzyć obiektów wewnątrz metody paint. Canvas, Paint, Path, Rect, Offset i inne obiekty rysowania powinny być tworzone w konstruktorze CustomPainter lub buforowane. Tworzenie obiektów wewnątrz paint prowadzi do alokacji na każdej klatce, co przy 60 klatkach na sekundę powoduje częste zbieranie pamięci i szarpanie animacji (jank).

Buforowanie Path znacznie przyspiesza renderowanie powtarzających się złożonych konturów. Jeśli kontur nie zmienia się między klatkami (np. siatka tła), utwórz Path raz i używaj go wielokrotnie. RepaintBoundary — widget-opakowanie, który zapobiega przerysowaniu CustomPainter przy zmianie nadrzędnego drzewa widgetów. Jeśli CustomPainter jest statyczny, opakuj go w RepaintBoundary w celu izolacji.

Dla animowanych CustomPainter z częstym przerysowaniem używaj Ticker lub AnimationController z shouldRepaint zwracającym true przy zmianie animowanej wartości. Optymalnie jest porównywać animowane pole w shouldRepaint, a nie przerysowywać na każdej klatce bez potrzeby. Rect caching dla złożonych gradientów: jeśli gradient nie zmienia kolorów, można buforować Shader przez Paint.shader.

Często zadawane pytania

Jaka jest różnica między CustomPainter a Canvas we Flutter?

CustomPainter — to klasa-kontrakt określająca, co i jak rysować na Canvas. Canvas — sam obiekt rysowania, który udostępnia metody (drawCircle, drawLine itd.). CustomPainter otrzymuje Canvas przez metodę paint i zarządza jego użyciem. Jeden Canvas może być używany przez różne CustomPainter w różnym czasie.

Czy można używać CustomPainter bez CustomPaint?

Nie, CustomPainter nie ma sensu bez CustomPaint. CustomPaint to widget, który wywołuje metodę paint CustomPainter i wyświetla wynik na ekranie. Bez CustomPaint Canvas nie zostanie utworzony, a paint nie zostanie wykonany. Można jednak zaimplementować własny RenderObject z Canvas, ale jest to podejście niższego poziomu.

Jak animować CustomPainter?

Użyj AnimationController z Ticker, przekazując animowaną wartość do CustomPainter przez parametry konstruktora. Przy zmianie wartości wywołuj setState() w widgetcie zawierającym CustomPaint, co wyzwala shouldRepaint. Jeśli shouldRepaint zwraca true, system wywołuje paint dla nowej klatki animacji.

Dlaczego CustomPainter nie aktualizuje się przy zmianie danych?

Przyczyną prawie zawsze jest shouldRepaint zwracający false. Sprawdź, czy pola oldDelegate są poprawnie porównywane z nowymi wartościami. Jeśli używany jest mutable object, shouldRepaint może nie zauważyć zmian, ponieważ referencja do obiektu pozostaje ta sama. Użyj == lub niezmiennych obiektów do poprawnego porównania.

Czy można używać CustomPainter do animacji w ReorderableListView?

Tak, CustomPainter jest używany do rysowania niestandardowych dekoracji w złożonych listach. Należy jednak pamiętać, że przy zmianie kolejności elementów RepaintBoundary wewnątrz ListView buforuje renderowanie każdego elementu. CustomPainter powinien być lekki i nie zawierać ciężkich operacji w paint, aby nie spowalniać animacji przeciągania.

Podsumowanie

  • CustomPainter — abstrakcyjna klasa Flutter do niestandardowego renderowania 2D z metodami paint i shouldRepaint
  • CustomPaint — widget przyjmujący painter (tło) i foregroundPainter (pierwszy plan) do wyświetlania grafiki
  • Canvas — obiekt z metodami rysowania: drawLine, drawCircle, drawPath, drawRect, drawArc i innymi
  • Paint — klasa określająca styl rysowania: kolor, grubość, styl (fill/stroke), przezroczystość i tryb mieszania
  • shouldRepaint — metoda optymalizacyjna zwracająca true tylko przy zmianie pól wpływających na renderowanie
  • Tworzenie obiektów wewnątrz paint jest zabronione — wszystkie Paint, Path, Rect muszą być buforowane dla 60 fps
  • RepaintBoundary — widget do izolacji CustomPainter od przerysowań nadrzędnego drzewa widgetów

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ż