Even after inheriting from View
for custom drawing code, we are limited to Android's determination and frequency to actually redraw the view.
Drawing on a View
instance requires that the drawing occurs within the UI update cycles as well as on the UI thread. This provides limitations that can be avoided by drawing on a SurfaceView
instance from another thread:
SurfaceView
instance, we need a Surface
instance to draw on. Thus, we need to know when the surface is created and destroyed:public class MainActivity : Activity, ISurfaceHolderCallback { private ISurfaceHolder surfaceHolder; public void SurfaceChanged( ISurfaceHolder holder, Format format, int width, int height) { } public void SurfaceCreated(ISurfaceHolder holder) { surfaceHolder = holder; } public void SurfaceDestroyed(ISurfaceHolder holder) { surfaceHolder = null } }
ISurfaceHolder
instance when the activity is created:var holder = surfaceView.Holder; holder.AddCallback(this);
Canvas
as frequently as we need:cancellation = new CancellationTokenSource(); var token = cancellation.Token; Task.Run(() => { while (!token.IsCancellationRequested) { // ... draw on the canvas ... } }, cancellation.Token);
LockCanvas()
method. We can then draw as usual. When we are finished, we must ensure that we write the Canvas
instance to the Surface
instance using the UnlockCanvasAndPost()
method:Canvas canvas = null; try { canvas = holder.LockCanvas(); // ... draw as normal with the canvas ... } catch (Exception ex) { // handle errors } finally { if (canvas != null) { holder.UnlockCanvasAndPost(canvas); } }
cancellation.Cancel();
If we want to create a game or app that requires high performance and frequent updates to a view, we can simply inherit from View
and override the OnDraw()
method. However, we are limited to Android's determination and frequency to update the view. Invoking Invalidate()
does not guarantee that the view will be immediately refreshed. Also, we can only draw on the canvas from the UI thread.
If we want to have a dedicated thread and surface for drawing on, we must use a SurfaceView
instance instead. Drawing on a surface is very similar to drawing on a view in that we execute the same logic for drawing on a Canvas
, but differs in where we get the Canvas
from. When we inherit from a View
instance, we override the OnDraw()
method and make use of the canvas argument. When we draw on a Surface
instance, we request a canvas from the ISurfaceHolder
instance.
Just like when we want to render a video or camera preview, we need a Surface
instance to draw on, we can either add a SurfaceView
instance to the layout, or we can inherit from SurfaceView
and add an instance of the new type to the layout. We then need an instance of the surface holder, which we obtain from the Holder
property of the surface. We attach an implementation of ISurfaceHolderCallback
, which will provide us with the actual surface holder.
As usual, when working with surfaces, we need to keep a reference of the holder from the SurfaceCreated()
method and ensure that we stop the drawing in the SurfaceDestroyed()
method.
When we have a surface holder, we can request a canvas using the LockCanvas()
method. We can only request, and lock, one canvas instance at a time. If LockCanvas()
is invoked again before the canvas is released, this method will throw an exception. To release the canvas, and at the same time draw the canvas onto the surface, we invoke the UnlockCanvasAndPost()
method with the canvas returned by the LockCanvas()
method.
If we were making a game, or some app that required high-performance updates, we would start a new thread and initiate a loop that would continuously update and draw on the canvas acquired from the surface holder.
Instead of drawing 2D images on a canvas, we can draw in 3D using OpenGL. OpenGL ES is a graphics library that support for high-performance 2D and 3D graphics. Instead of drawing on a SurfaceView
instance, we draw on a GLSurfaceView
instance, which inherits from SurfaceView
, through a renderer. We use GLSurfaceView.Renderer
to draw a frame instead of a canvas. Drawing with OpenGL is very different to drawing on a canvas, only similar in that drawing occurs on a surface instead of a View
instance. However, for 3D games, OpenGL must be used.
We can also draw in 3D using a framework that uses OpenGL. An example would be to make use of MonoGame. This is a game engine that makes it easier to make games as well as provide cross-platform support. Games written using MonoGame can usually run without much, if any, modification on iOS, Windows Phone, Windows, Mac OS X, and Linux.