Hello, Habravchane!
When working with 2D graphics in Android, you can draw using Canvas. The easiest way to do this is with the help of its class inherited from View. You just need to describe the onDraw () method, and use the canvas provided as a parameter to perform all the necessary actions. However, this approach has its drawbacks.
The onDraw () method is called by the system. You can manually use the invalidate () method, which tells the system to redraw it. But calling invalidate () does not guarantee an immediate call to the onDraw () method. Therefore, if we need to constantly do the drawing (for example, for any game), the above described method can hardly be considered suitable.
There is another approach - using the SurfaceView class. After reading the official guide and having studied a few examples, I decided to write a small article in Russian, which may help someone more quickly get used to this method of rendering. The article is designed for beginners. No complicated and clever tricks are described here.
SurfaceView class
The feature of the SurfaceView class is that it provides a separate area for drawing, actions with which should be moved to a separate application flow. Thus, the application does not need to wait until the system is ready to draw the entire hierarchy of view-elements. An auxiliary stream can use the canvas of our SurfaceView to render as fast as necessary.
')
The whole implementation comes down to two main points:
- Creating a class inherited from SurfaceView and implementing the SurfaceHolder.Callback interface
- Creating a stream that will manage the rendering.
Class creation
As mentioned above, we need a class that extends SurfaceView and implements the SurfaceHolder.Callback interface. This interface offers to implement three methods: surfaceCreated (), surfaceChanged () and surfaceDestroyed (), called respectively when creating an area for drawing, changing it and destroying it.
Working with the drawing canvas is not carried out directly through the class we created, but with the help of the SurfaceHolder object. You can get it by calling the getHolder () method. This object will provide us with a canvas for drawing.
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { public MySurfaceView(Context context) { super(context); getHolder().addCallback(this); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }
In the class constructor, we get the SurfaceHolder object and, using the addCallback () method, we specify that we want to receive the corresponding callbacks.
In the surfaceCreated () method, as a rule, you need to start drawing and in the surfaceDestroyed () method, on the contrary, to complete it. Let us leave the bodies of these methods for now empty and implement the stream responsible for rendering.
Rendering stream implementation
Create a class inherited from Thread. In the constructor, it will take two parameters: SurfaceHolder and Resources to load the image that we will draw on the screen. Also in the class we need a variable flag indicating that the drawing is done and a method to set this variable. And, of course, you need to override the run () method.
As a result, we get the following class:
class DrawThread extends Thread{ private boolean runFlag = false; private SurfaceHolder surfaceHolder; private Bitmap picture; private Matrix matrix; private long prevTime; public DrawThread(SurfaceHolder surfaceHolder, Resources resources){ this.surfaceHolder = surfaceHolder;
In order for the result not to look very fresh, the downloaded image will be tripled and slightly shifted to the center of the screen. We do this with the transformation matrix. Also, no more than once every 30 milliseconds, we will rotate the image by 2 degrees around its center. Drawing on the canvas itself is better, of course, in a separate method, but in this case we just clear the screen and draw the image. So you can leave as is.
Start and Finish Render
Now, after we have written the thread that manages the drawing, let's go back to editing our SurfaceView class. In the surfaceCreated () method, create a stream and start it. And in the surfaceDestroyed () method, complete its work. As a result, the MySurfaceView class will look like
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { private DrawThread drawThread; public MySurfaceView(Context context) { super(context); getHolder().addCallback(this); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { drawThread = new DrawThread(getHolder(), getResources()); drawThread.setRunning(true); drawThread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { boolean retry = true;
It should be noted that the creation of a stream must be performed in the surfaceCreated () method. In the LunarLander example from the official documentation, the rendering stream is created in the class constructor inherited from SurfaceView. But with this approach, an error may occur. If you minimize the application by pressing the Home key on the device and then open it again, an IllegalThreadStateException is thrown.
Activity applications might look like this:
public class SurfaceViewActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MySurfaceView(this)); } }
The result of the program looks like this (because of the rotation, the image is slightly smeared, but it looks quite acceptable on the device):

Conclusion
The above described method of rendering graphics is somewhat more complicated than drawing a simple View on a canvas, but in some cases it is more preferable. The difference can be noticed if the application needs to redraw the graphics very often. Auxiliary flow helps to better control this process.
And in conclusion, I would like to provide links to some of the resources that I used when creating the article:
- An example of the game LunarLander from the official documentation
- An article describing how to use SurfaceView.