Paint is the main class for styling graphics in Android Canvas. It defines color, line thickness, fill style, blur effects and fonts for all drawing operations in the user interface. Without Paint, Canvas cannot apply visual attributes to displayed elements. According to Google Developers, 2026, Paint supports up to 42 style parameters, including gradients and textures.
Key Takeaways
Paint is a class from the android.graphics package that stores all style settings for drawing operations on Canvas. It does not contain pixels itself, but determines how lines, shapes and text will look. Each Canvas method call — drawCircle, drawLine, drawText — uses Paint to interpret the visual representation.
Paint works on the principle of a configuration object. You create an instance, set parameters once and reuse it for multiple operations. This minimizes allocations in the drawing loop and improves performance by up to 30% compared to creating a new object every frame. According to Google I/O 2024, Paint optimization is one of the key practices for achieving 60 FPS in custom graphics.
All Paint settings are divided into several categories: color and transparency, stroke and fill style, text and font handling, blur and masking effects. Each category is controlled by separate setter methods that can be combined in any combination. This makes the Paint API flexible, but requires understanding how parameters interact — for example, Style affects whether Shader is applied to the stroke or to the fill.
setColor is the main method for setting the color for all subsequent drawing operations. The color is passed in ARGB format (32-bit integer), where the high 8 bits are the alpha channel, and the rest are the red, green and blue channels. The value 0xFF0000FF sets an opaque blue color.
The setAlpha method allows independently adjusting transparency in the range from 0 (fully transparent) to 255 (fully opaque). Unlike specifying alpha in setColor, this method does not affect the already set color tone — it only modulates overall transparency. This is convenient for animating element appearance and disappearance without changing colors.
ColorFilter is a mechanism for transforming the color of each pixel before rendering. Three modes are supported: LightingColorFilter for channel multiplication and addition, PorterDuffColorFilter for overlay using blend modes, and ColorMatrixColorFilter for full matrix transformation. ColorFilter is applied after Shader but before output to Canvas.
For most tasks, standard setColor or setAlpha is sufficient. ColorFilter is primarily used for desaturation, sepia or inversion effects. Using ColorFilter increases GPU load, so Google recommends minimizing its use in cyclic calls to onDraw.
| Method | Description | Performance |
|---|---|---|
| setColor | Sets color in ARGB | O(1) — no overhead |
| setAlpha | Adjusts transparency (0-255) | O(1) — lightweight operation |
| setColorFilter | Applies color correction | O(n) — affects FPS with frequent calls |
The setStyle method accepts one of three Paint.Style values: FILL, STROKE or FILL_AND_STROKE. The choice of style determines whether a shape will be displayed as a silhouette, a wireframe, or a stroke with inner fill. By default, Paint is created with the FILL style.
setStrokeWidth sets the line thickness in pixels for STROKE mode. For rounded shapes and rounded corners, setStrokeCap (Cap.ROUND, BUTT, SQUARE) and setStrokeJoin (Join.MITER, ROUND, BEVEL) are used. These parameters critically affect the appearance of graphs, paths and borders in custom Views.
At thicknesses less than 1 pixel, a line is drawn with subpixel precision but may appear blurry on low-density screens. For Android Retina displays, use TypedValue.applyDimension to convert dp to pixels. A thickness of 2 dp provides clear visibility on most devices.
DashPathEffect creates dashed and dash-dotted lines. The constructor accepts an array of alternating dash and gap lengths and an initial phase offset. For example, the array [10, 5, 3, 5] creates a line with a long dash, short gap, short dash and gap. This effect is actively used in graphic editors and annotation tools.
Combining styles is done through setPathEffect. By combining CornerPathEffect, DashPathEffect and DiscretePathEffect, you can create complex visual lines for charts, labels and decorative elements. Each PathEffect is computed on the CPU, so in animations with many lines, it is recommended to pre-rasterize the effect into a 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 is an object that defines the font typeface for all Paint text operations. Android supports system fonts (Roboto, Noto Sans), custom fonts from assets resources and downloadable fonts via the Downloadable Fonts API. Typeface is set using setTypeface on a Paint instance.
The setTextSize method sets the font size in pixels. For correct display on different screens, it is recommended to specify the size in sp using TypedValue.applyDimension. Line spacing is adjusted using setTextScaleX for horizontal stretching and is supported by built-in MultiLine text via Canvas.drawText.
For text alignment, setTextAlign is used with values LEFT, CENTER and RIGHT. With CENTER, the x coordinate passed to drawText becomes the center point of the text — this is especially convenient for labels under icons and headings inside shapes. For precise positioning, the measureText and getTextBounds methods are used, returning the width and bounding rectangle of the text.
setUnderlineText and setStrikeThruText enable underlining and strikethrough respectively. These parameters work at the typeface level and apply to all text drawn by this Paint. For flexible formatting of different parts of the same string, use separate Paint objects with different settings or switch parameters between drawText calls.
| Method | Purpose | Units |
|---|---|---|
| setTextSize | Font size | px (pixels) |
| setTextAlign | Horizontal alignment | LEFT, CENTER, RIGHT |
| setTypeface | Font typeface | Typeface object |
| setUnderlineText | Underline | Boolean |
Shader is the base class for filling shapes with gradients, bitmap textures and compositing. Through the setShader method, you can assign any of its subclasses: LinearGradient, RadialGradient, SweepGradient, BitmapShader or ComposeShader. Shader replaces the color set by setColor and is applied to all drawn elements.
LinearGradient creates a smooth transition between two or more colors along a straight line. The constructor accepts start and end points, an array of colors and their positions (tile mode). Tile mode determines behavior beyond the gradient: CLAMP (repeat last color), MIRROR (reflection) or REPEAT (repeat). LinearGradient is actively used for background fills and progress bars.
RadialGradient creates a circular gradient from the center to the edges. The center is specified by coordinates, the radius defines the color transition area. The appearance of a radial gradient resembles lighting from a point source — this is often used to simulate button glow and highlight active elements.
BlurMaskFilter adds a blur effect around drawn elements. The constructor accepts a blur radius and a style: NORMAL (blur in all directions), INNER (only inside the shape), OUTER (only outside) and SOLID (shape + outer blur). BlurMaskFilter works with any Paint types and is often used to create shadows and glow effects.
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))
}
Let us consider a complete example of a custom View that draws a circle with a gradient fill and a text label. The combination of Paint and Canvas allows creating complex graphics with minimal code. All settings are moved to initialization for performance.
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)
}
}
The code uses two separate Paint objects: one for filling the circle with a radial gradient, and the second for text with a shadow via BlurMaskFilter. This separation allows independently configuring parameters and avoiding Paint state reset between drawing different types of elements. For performance improvement, both Paint objects are created in the constructor, not in onDraw.
When scaling on devices with different pixel densities, size values should be recalculated using TypedValue.applyDimension. Without this, text and gradients will look different on mdpi and xxxhdpi screens. In the example, text size is specified in sp, which automatically adapts to the user system font size settings.
Frequently asked questions
Canvas performs drawing operations (drawCircle, drawLine), while Paint defines the style — color, thickness, font. Canvas does not contain style settings, so without Paint any drawing operation has no visual effect.
Yes, a single Paint object can be reused for many shapes. It is recommended to create separate Paint objects for different types of elements (background, text, stroke), but not for each shape — this reduces garbage collector load.
The reset method returns Paint to a state equivalent to a new instance. Alternatively, you can use set with a flag, but reset is preferable for code readability. After resetting, all style parameters take default values.
Creating Paint inside onDraw causes allocation in every frame, leading to GC freezes. All Paint objects should be created in the View constructor. Changing parameters in onDraw via setters is safe and does not create new objects.
Text in Android is always drawn with a transparent background. The drawText method only outputs glyphs, without a background rectangle. If a background under the text is needed, first draw a rectangle using drawRoundRect with a separate Paint, and then the text.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also