Drawing on the canvas of a SurfaceView

Even after inheriting from View for custom drawing code, we are limited to Android's determination and frequency to actually redraw the view.

How to do it...

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:

  1. In order to draw on a 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
      }
    }
  2. Then, we need to get hold of the ISurfaceHolder instance when the activity is created:
    var holder = surfaceView.Holder;
    holder.AddCallback(this);
  3. Once we have a surface to draw on, we can start a new thread to draw on the Canvas as frequently as we need:
    cancellation = new CancellationTokenSource();
    var token = cancellation.Token;
    Task.Run(() => {
      while (!token.IsCancellationRequested) {
        // ... draw on the canvas ...
      }
    }, cancellation.Token);
  4. In order to obtain a canvas, we use the 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);
      }
    }
  5. When the surface is destroyed, we must stop the draw thread:
    cancellation.Cancel();

How it works...

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.

Tip

The Invalidate() method does not guarantee that the View will immediately be redrawn, rather it is a request to redraw as soon as possible.

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.

Note

One of the main features of drawing on a surface is the ability to draw from a thread other than the UI thread.

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.

There's more...

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.243.184