Paint — główna klasa do stylizacji grafiki w Android Canvas. Określa kolor, grubość linii, styl wypełnienia, efekty rozmycia i czcionki dla wszystkich operacji rysowania w interfejsie użytkownika. Bez Paint Canvas nie może stosować atrybutów wizualnych do wyświetlanych elementów. Według Google Developers, 2026, Paint obsługuje do 42 parametrów stylu, w tym gradienty i tekstury.
Najważniejsze
Paint — klasa z pakietu android.graphics, która przechowuje wszystkie ustawienia stylów dla operacji rysowania na Canvas. Nie zawiera samych pikseli, ale określa, jak będą wyglądać linie, kształty i tekst. Każde wywołanie metody Canvas — drawCircle, drawLine, drawText — używa Paint do interpretacji reprezentacji wizualnej.
Paint działa na zasadzie obiektu konfiguracyjnego. Tworzysz instancję, ustawiasz parametry raz i używasz jej wielokrotnie dla wielu operacji. Minimalizuje to liczbę alokacji w pętli rysowania i zwiększa wydajność nawet o 30% w porównaniu z tworzeniem nowego obiektu w każdej klatce. Według Google I/O 2024, optymalizacja Paint to jedna z kluczowych praktyk osiągnięcia 60 FPS w niestandardowej grafice.
Wszystkie ustawienia Paint dzielą się na kilka kategorii: kolor i przezroczystość, styl obrysu i wypełnienia, praca z tekstem i czcionkami, efekty rozmycia i maskowania. Każda kategoria jest zarządzana przez osobne metody-settery, które można łączyć w dowolnych kombinacjach. To sprawia, że API Paint jest elastyczne, ale wymaga zrozumienia interakcji parametrów — na przykład Style wpływa na to, czy Shader jest stosowany do konturu, czy do wypełnienia.
setColor — główna metoda ustawiania koloru dla wszystkich kolejnych operacji rysowania. Kolor jest przekazywany w formacie ARGB (32-bitowa liczba całkowita), gdzie starsze 8 bitów to kanał alfa, a pozostałe to kanały czerwony, zielony i niebieski. Wartość 0xFF0000FF ustawia nieprzezroczysty kolor niebieski.
Metoda setAlpha pozwala niezależnie regulować przezroczystość w zakresie od 0 (całkowicie przezroczysty) do 255 (całkowicie nieprzezroczysty). W przeciwieństwie do określania alfy w setColor, ta metoda nie wpływa na już ustawiony odcień koloru — moduluje jedynie ogólną przezroczystość. Jest to wygodne do animacji pojawiania się i znikanie elementów bez zmiany koloru.
ColorFilter — mechanizm transformacji koloru każdego piksela przed rysowaniem. Obsługiwane są trzy tryby: LightingColorFilter do mnożenia i dodawania kanałów, PorterDuffColorFilter do nakładania z użyciem trybów mieszania oraz ColorMatrixColorFilter do pełnej transformacji macierzowej. ColorFilter jest stosowany po Shader, ale przed wyświetleniem na Canvas.
Większość zadań wymaga jedynie standardowego setColor lub setAlpha. ColorFilter jest używany głównie do efektów odbarwiania, sepii lub inwersji. Stosowanie ColorFilter zwiększa obciążenie GPU, dlatego Google zaleca minimalizowanie jego użycia w cyklicznych wywołaniach onDraw.
| Metoda | Opis | Wydajność |
|---|---|---|
| setColor | Ustawia kolor w ARGB | O(1) — bez narzutu |
| setAlpha | Reguluje przezroczystość (0-255) | O(1) — lekka operacja |
| setColorFilter | Stosuje korekcję kolorów | O(n) — wpływa na FPS przy częstych wywołaniach |
Metoda setStyle przyjmuje jedną z trzech wartości Paint.Style: FILL (wypełnienie), STROKE (kontur) lub FILL_AND_STROKE (jednocześnie). Od wyboru stylu zależy, czy figura będzie wyświetlana jako sylwetka, jako szkielet, czy jako kontur z wewnętrznym wypełnieniem. Domyślnie Paint jest tworzony ze stylem FILL.
setStrokeWidth ustawia grubość linii w pikselach dla trybu STROKE. Dla zaokrąglonych figur i zaokrąglonych rogów używa się setStrokeCap (Cap.ROUND, BUTT, SQUARE) i setStrokeJoin (Join.MITER, ROUND, BEVEL). Te parametry mają krytyczny wpływ na wygląd wykresów, ścieżek i ramek w niestandardowych View.
Przy grubości mniejszej niż 1 piksel linia jest rysowana z dokładnością subpikselową, ale może wyglądać rozmazano na ekranach o niskiej gęstości. Dla wyświetlaczy Retina w Android używaj TypedValue.applyDimension do przeliczenia dp na piksele. Grubość 2 dp zapewnia wyraźną widoczność na większości urządzeń.
DashPathEffect tworzy linie przerywane i kreskowane. Konstruktor przyjmuje tablicę długości naprzemiennych kresek i przerw oraz początkowe przesunięcie fazy. Na przykład tablica [10, 5, 3, 5] tworzy linię z długą kreską, krótką przerwą, krótką kreską i przerwą. Ten efekt jest aktywnie używany w edytorach graficznych i narzędziach do adnotacji.
Łączenie stylów odbywa się przez setPathEffect. Kombinując CornerPathEffect, DashPathEffect i DiscretePathEffect, można tworzyć złożone linie wizualne dla diagramów, podpisów i elementów dekoracyjnych. Każdy PathEffect jest obliczany na CPU, dlatego w animacjach z dużą liczbą linii zaleca się wcześniejsze rasteryzowanie efektu do Bitmap.
val paint = Paint().apply {
setStyle(Paint.Style.STROKE)
setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics))
setPathEffect(DashPathEffect(floatArrayOf(10f, 5f, 3f, 5f), 0f))
}
Typeface — obiekt określający krój pisma dla wszystkich operacji tekstowych Paint. Android obsługuje czcionki systemowe (Roboto, Noto Sans), niestandardowe czcionki z zasobów assets oraz czcionki ładowane przez Downloadable Fonts API. Ustawienie Typeface odbywa się przez setTypeface na instancji Paint.
Metoda setTextSize ustawia rozmiar czcionki w pikselach. Do prawidłowego wyświetlania na różnych ekranach zaleca się podawanie rozmiaru w sp za pomocą TypedValue.applyDimension. Odstęp między wierszami jest regulowany przez setTextScaleX do rozciągania poziomego i jest objęty wbudowaną obsługą tekstu MultiLine przez Canvas.drawText.
Do wyrównywania tekstu używa się setTextAlign z wartościami LEFT, CENTER i RIGHT. Przy CENTER współrzędna x przekazana do drawText staje się punktem środkowym tekstu — jest to szczególnie wygodne dla podpisów pod ikonami i nagłówków wewnątrz figur. Do precyzyjnego pozycjonowania stosuje się metody measureText i getTextBounds, które zwracają szerokość i prostokąt granic tekstu.
setUnderlineText i setStrikeThruText włączają odpowiednio podkreślenie i przekreślenie. Te parametry działają na poziomie kroju pisma i są stosowane do całego tekstu rysowanego przez dany Paint. Do elastycznego formatowania różnych części jednego wiersza używaj osobnych obiektów Paint z różnymi ustawieniami lub przełączaj parametry między wywołaniami drawText.
| Metoda | Przeznaczenie | Jednostki miary |
|---|---|---|
| setTextSize | Rozmiar czcionki | px (piksele) |
| setTextAlign | Wyrównanie poziome | LEFT, CENTER, RIGHT |
| setTypeface | Krój pisma | Typeface object |
| setUnderlineText | Podkreślenie | Boolean |
Shader — klasa bazowa do wypełniania figur gradientami, teksturami bitmapowymi i kompozytami. Poprzez metodę setShader można przypisać dowolną z podklas: LinearGradient, RadialGradient, SweepGradient, BitmapShader lub ComposeShader. Shader zastępuje kolor ustawiony przez setColor i jest stosowany do wszystkich rysowanych elementów.
LinearGradient tworzy płynne przejście między dwoma lub więcej kolorami wzdłuż linii prostej. Konstruktor przyjmuje punkt początkowy i końcowy, tablicę kolorów i ich pozycje (tryb kafelkowania). Tryb kafelkowania określa zachowanie poza gradientem: CLAMP (powtórzenie ostatniego koloru), MIRROR (odbicie) lub REPEAT (powtórzenie). LinearGradient jest aktywnie używany do wypełnień tła i pasków postępu.
RadialGradient tworzy gradient kołowy od środka do krawędzi. Środek jest określany przez współrzędne, promień definiuje obszar przejścia kolorów. Wygląd gradientu radialnego przypomina oświetlenie od punktowego źródła światła — jest to często używane do imitacji świecenia przycisków i podświetlenia aktywnych elementów.
BlurMaskFilter dodaje efekt rozmycia wokół rysowanych elementów. Konstruktor przyjmuje promień rozmycia i styl: NORMAL (rozmycie we wszystkich kierunkach), INNER (tylko wewnątrz figury), OUTER (tylko na zewnątrz) i SOLID (figura + rozmycie zewnętrzne). BlurMaskFilter działa z dowolnymi typami Paint i jest często używany do tworzenia cieni i efektów świecenia.
val paint = Paint().apply {
setShader(LinearGradient(
0f, 0f, width, height,
intArrayOf(Color.RED, Color.BLUE),
null,
Shader.TileMode.CLAMP
))
setMaskFilter(BlurMaskFilter(20f, BlurMaskFilter.Blur.NORMAL))
}
Rozważmy pełny przykład niestandardowego View, który rysuje okrąg z gradientowym wypełnieniem i podpisem tekstowym. Kombinacja Paint i Canvas pozwala tworzyć złożoną grafikę z minimalną ilością kodu. Wszystkie ustawienia są wyniesione do inicjalizacji dla wydajności.
class GradientCircleView@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val fillPaint = Paint().apply {
setShader(RadialGradient(
0f, 0f, 200f,
intArrayOf(Color.CYAN, Color.DKGRAY),
null, Shader.TileMode.CLAMP
))
}
private val textPaint = Paint().apply {
setColor(Color.WHITE)
setTextSize(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 24f, resources.displayMetrics
))
setTextAlign(Paint.Align.CENTER)
setMaskFilter(BlurMaskFilter(5f, BlurMaskFilter.Blur.NORMAL))
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(200f, 200f, 150f, fillPaint)
canvas.drawText("Gradient", 200f, 210f, textPaint)
}
}
W kodzie użyto dwóch oddzielnych obiektów Paint: jeden do wypełnienia koła gradientem radialnym, drugi do tekstu z cieniem przez BlurMaskFilter. Takie rozdzielenie pozwala niezależnie konfigurować parametry i unikać resetowania stanu Paint między rysowaniem różnych typów elementów. Dla zwiększenia wydajności oba Paint są tworzone w konstruktorze, a nie w onDraw.
Przy skalowaniu na urządzeniach z różną gęstością pikseli wartości rozmiarów powinny być przeliczone przez TypedValue.applyDimension. Bez tego tekst i gradient będą wyglądać inaczej na ekranach mdpi i xxxhdpi. W przykładzie rozmiar tekstu jest ustawiony w sp, co automatycznie dostosowuje się do systemowych ustawień rozmiaru czcionki użytkownika.
Często zadawane pytania
Canvas wykonuje operacje rysowania (drawCircle, drawLine), a Paint określa styl — kolor, grubość, czcionkę. Canvas nie zawiera ustawień stylu, dlatego bez Paint żadna operacja rysowania nie ma efektu wizualnego.
Tak, jeden obiekt Paint można wielokrotnie używać dla wielu kształtów. Zaleca się tworzenie osobnych Paint dla różnych typów elementów (tło, tekst, kontur), ale nie dla każdego kształtu — zmniejsza to obciążenie garbage collectora.
Metoda reset przywraca Paint do stanu równoważnego nowej instancji. Alternatywnie można użyć set z flagą, ale reset jest preferowany dla czytelności kodu. Po resecie wszystkie parametry stylu przyjmują wartości domyślne.
Tworzenie Paint wewnątrz onDraw powoduje alokację w każdej klatce, co prowadzi do zatrzymań GC. Wszystkie obiekty Paint powinny być tworzone w konstruktorze View. Zmiana parametrów w onDraw przez settery jest bezpieczna i nie tworzy nowych obiektów.
Tekst w Android zawsze rysuje się z przezroczystym tłem. Metoda drawText wyświetla tylko glify, bez prostokąta tła. Jeśli potrzebujesz tła pod tekstem, najpierw narysuj prostokąt przez drawRoundRect z osobnym Paint, a następnie tekst.
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ż