The graphical interface of modern applications on Android is impossible without the use of powerful rendering tools, among which class Canvasoccupies a central place. It is a virtual canvas provided by the system on which the developer can draw primitives, text, images and complex vector shapes. Understanding how it works is a fundamental skill for creating custom Views, games, photo editing tools and any interfaces that go beyond standard widgets. android.graphics.Canvas, is a fundamental skill for creating custom Views, games, photo editing tools, and any interfaces that go beyond standard widgets.

The drawing process is always closely related to the object Paintwhich acts as a โ€œbrushโ€. It is the Paint settings that determine the color, line thickness, fill style and anti-aliasing. Without the correct configuration of this link, even the most complex drawing algorithm will produce pale or sloppy results. In this article, we'll break down drawing architecture, look at specific techniques, and discuss performance.

Have you ever wondered why some applications run smoothly when rendering complex graphics, while others lag? The secret often lies in how Canvas is used. Unoptimized code can kill FPS even on a top-end smartphone. Let's dive into the technical details of the implementation of the graphics pipeline.

Drawing architecture: Canvas and Paint

The basic philosophy of drawing in Android is based on the division of responsibility between the surface (Canvas) and the tool (Paint). Object Canvas provides methods for geometric operations such as drawLine, drawCircle or drawRect. However, Canvas itself does not know what color to draw or how thick the line should be. These parameters are encapsulated in an object android.graphics.Paint.

When you call the paint method, you pass an instance of Paint as an argument. The system reads the current state of this "brush" and applies it to the raster buffer. It is important to understand that creating a new Paint object inside a method onDraw() is a serious mistake. Memory allocation During rendering, it causes the Garbage Collector to work, which leads to micro-freezes of the interface.

Initialization of graphic objects should be carried out in the constructor of your View or in the initialization block. This ensures that by the time the first draw call is made, all resources are ready. Let's look at the basic structure of a custom View:

public class CustomView extends View {

private Paint paint;

public CustomView(Context context) {

super(context);

init();

}

private void init() {

paint = new Paint();

paint.setColor(Color.BLUE);

paint.setStyle(Paint.Style.STROKE);

paint.setStrokeWidth(5f);

// Enable anti-aliasing for high-quality graphics

paint.setAntiAlias(true);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

// Draw a line using a pre-created paint

canvas.drawLine(0, 0, getWidth(), getHeight(), paint);

}

}

Pay attention to the call paint.setAntiAlias(true). This setting enables edge smoothing (anti-aliasing), making lines and circles visually pleasing without "laddering" on the edges. Disabling this option may improve performance slightly, but at the cost of image quality.

๐Ÿ’ก

Always move the creation of Paint and Path objects outside of the onDraw() method. This is critical to maintaining a stable 60 FPS in animations.

Basic primitives and drawing styles

The set of methods of the Canvas class covers all basic geometric shapes. You can draw points, lines, rectangles, ovals, arcs and free-form paths. Each method requires coordinates, which are specified in pixels relative to the top left corner of the View. The coordinate system in Android has the X axis pointing to the right and the Y axis pointing down.

The drawing style is controlled through the paint.setStyle()method. There are three main modes: FILL (fill), STROKE (outline) and FILL_AND_STROKE (both at once). The choice of style radically changes the perception of the object. For example, a circle in STROKE mode will turn into a ring, and in FILL mode it will turn into a solid disk.

  • ๐Ÿ”ต drawCircle: Draws a circle based on the center (x, y) and radius.
  • ๐ŸŸฅ drawRect: Creates a rectangle defined by the coordinates of the upper left and lower right corners.
  • โž– drawLine: Draws a line between two points (startX, startY) and (stopX, stopY).
  • ๐Ÿ”บ drawPath: Allows you to draw complex shapes consisting of a sequence of lines and curves.

The class android.graphics.Pathis used to draw complex shapes. You create a path by moving the "pen" to the desired point with the command moveTo, and then adding lines or Bezier curves. The finished Path is transferred to Canvas in the same way as simple primitives. This opens up opportunities for creating icons, graphs and logos programmatically.

๐Ÿ“Š What type of graphics do you most often use in your projects?
Simple shapes (circles, squares)
Text and fonts
Complex ways (Path)
Raster images (Bitmap)
3D transformations

Working with text and fonts

Rendering text on Canvas is not just drawing a line, but the exact positioning of glyphs. The method drawText accepts a string, X and Y coordinates, and a Paint object. However, the Y coordinate in this method does not point to the top or bottom border of the text, but to baseline (baseline). This often causes confusion for beginners, as text may appear cut off or misaligned if you don't take into account font height.

For precise control of typography, use an object android.graphics.Paint.FontMetrics. It provides data on ascent, descent and overall line height. Knowing these metrics, you can center text both vertically and horizontally within any area. Horizontal alignment is set via paint.setTextAlign().

In modern versions of Android it is recommended to use the class StaticLayout for multi-line text, especially if you need to support different writing directions or complex formatting. However, for simple labels inside custom Views, the drawText method remains the most productive solution. Don't forget to cache text dimensions if you call paint.measureText() in a render loop.

โš ๏ธ Attention: Avoid using heavy fonts (.ttf/.otf) in large sizes unless necessary. Loading and rasterizing complex fonts can take a significant amount of time when you first launch the application, causing a delay in the display of the interface.

Transformations and state saving

Canvas has its own state stack, which allows you to apply temporary transformations. Methods translate, rotate, scale and skew change the canvas transformation matrix. All subsequent drawing operations will apply these changes. For example, rotating the canvas 45 degrees will cause all rectangles drawn after that to also be rotated.

It is critical to manage the state of the Canvas using pairs of methods canvas.save() and canvas.restore(). Before applying the transformation, you call save(), which saves the current matrix and clip settings to the stack. When a group of drawing operations completes, restore()is called, returning the Canvas to its original state. Without this, your transformations can โ€œaccumulateโ€ and break the rendering of other View elements.

Transformation method Description of action Typical application
translate(dx, dy) Shifts the origin Moving an object without changing it coordinates
rotate(degrees) Rotates the canvas Rotate clock hands, loading icons
scale(sx, sy) Scales coordinates Zoom images, adaptive graphics
clipRect(...) Limits the area drawing Optimization, trimming complex shapes

Use clipRect is also a powerful optimization tool. If you know that a certain part of the screen is not visible to the user (for example, it is blocked by another View or is outside the visible area when scrolling), you can limit the drawing area. Canvas will automatically ignore all drawing commands outside the clip, saving CPU resources.

How does the transformation matrix work?

The Canvas matrix is โ€‹โ€‹a 3x3 mathematical model. All transformations are multiplied. The order is important: rotation first, then translation will give a different result than translation first, then rotation.

Performance optimization and Hardware Acceleration

Starting with Android 3.0 (API 11), hardware acceleration (Hardware Acceleration) is enabled by default. This means that Canvas operations are performed on the GPU rather than the CPU. For most standard operations this gives a huge speed boost. However, there are Paint methods and settings that are not supported by the GPU and force the system to switch to software rendering, which dramatically reduces performance.

Such โ€œdangerousโ€ areas include the use of paint.setMaskFiltercertain types Shader and specific blend modes (Xfermode). If your application starts to slow down when rendering, the first thing to check is whether you are using these functions. Also, frequent creation of objects Bitmap or Path inside the rendering loop is the main reason for the drop in FPS.

For complex animation, consider using the class android.graphics.Canvas in conjunction with SurfaceView or TextureViewif the standard View cannot cope with the refresh rate. Games and editors often use a separate rendering thread to avoid blocking the main UI thread. But for standard interfaces, it is enough to properly optimize the code inside onDraw.

  • ๐Ÿš€ Avoid allocating new objects inside onDraw().
  • ๐ŸŽจ Use Bitmap.createBitmap() with caution, preferring caching.
  • โšก Check your GPU's support for operations through the Android Developers documentation.

โš ๏ธ Attention: Hardware acceleration may behave differently on emulators and real devices. Always test graphically rich interfaces on a physical smartphone, as emulators may not correctly reflect GPU performance.

๐Ÿ’ก

The golden rule of optimization: anything that does not change between frames should be drawn once and stored in the Bitmap (caching), rather than redrawing every frame.

Frequent errors and problem solving

One of the most common problems is a โ€œblack screenโ€ or lack of graphics. Often the reason lies in the fact that the Paint color matches the background color, or the call was forgotten (although in some cases its absence is even desirable for transparency). Also check if your View has non-zero dimensions. If the width or height is 0, the method super.onDraw(canvas) (although in some cases its absence is even desirable for transparency). Also check if your View has non-zero dimensions. If the width or height is 0, the method onDraw may not be called or may draw an invisible area.

Another common mistake is incorrect handling of pixel density. Coordinates and dimensions specified in the code as a hardcode (for example, 100) will look different on devices with different screen densities (dpi). Always use getResources().getDisplayMetrics().density to convert DP to pixels when initializing graphic objects. This will ensure the same visual size of elements on all screens.

Memory problems (OutOfMemoryError) when working with Bitmap are a classic of the genre. Loading a huge full resolution image to display in a small ImageView wastes memory. Use BitmapFactory.Options.inSampleSize to decode a small copy of the image corresponding to the size of the View.

โ˜‘๏ธ Diagnosing problems with Canvas

Done: 0 / 4

FAQ: Questions about drawing on Canvas

How to clear Canvas before a new frame?

Usually this does not need to be done manually, since the system clears the area before calling onDraw. However, if you are drawing on a Bitmap outside of the View, use the canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) method to completely clear the canvas.

Why is the text being drawn in a different place than I indicated?

Most likely, you are not taking the baseline into account. The Y coordinate in drawText points to the bottom of the main characters, not the top of the text rectangle. Use FontMetrics to calculate the correct offset.

Is it possible to draw on Canvas from a background thread?

No, you cannot directly draw on Canvas View from a background thread - this will throw an exception. To draw outside the UI thread, you need to use SurfaceView or TextureView, which provide access to the Canvas through a separate lock mechanism (lockCanvas).

How to draw a gradient?

To do this, you need to create an object LinearGradient or RadialGradient and install it in Paint via the paint.setShader(gradient)method. After this, all shapes drawn with this Paint will be filled with a gradient.