Continuous Rendering in the UI Thread

All we've done up until now is set the text of a TextView when needed. The actual rendering has been performed by the TextView itself. Let's create our own custom View whose sole purpose it is to let us draw stuff to the screen. We also want it to redraw itself as often as possible, and we want a simple way to perform our own drawing in that mysterious redraw method.

Although this may sound complicated, in reality Android makes it really easy for us to create such a thing. All we have to do is create a class that derives from the View class, and override a method called View.onDraw(). This method is called by the Android system every time it needs our View to redraw itself. Here's what that could look like:

class RenderView extends View {

public RenderView(Context context) { super(context);

protected void onDraw(Canvas canvas) { // to be implemented

Not exactly rocket science, is it? We get an instance of a class called Canvas passed to the onDraw() method. This will be our workhorse in the following sections. It lets us draw shapes and bitmaps to either another bitmap or a View (or a surface, which we'll talk about that in a bit).

We can use this RenderView as we'd use a TextView. We just set it as the content view of our activity and hook up any input listeners we need. However, it's not all that useful yet, for two reasons: it doesn't actually draw anything, and even if it did, it would only do so when the activity needed to be redrawn (i.e., when it is created or resumed, or when a dialog that overlaps it becomes invisible). How can we make it redraw itself?

Easy, like this:

protected void onDraw(Canvas canvas) { // all drawing goes here invalidate();

The call to the View.invalidate() method at the end of onDraw() will tell the Android system to redraw the RenderView as soon as it finds time to do that again. All this still happens on the UI thread, which is a bit of a lazy horse. But we actually have continuous rendering with the onDraw() method, albeit relatively slow continuous rendering. We'll fix that later; for now it suffices for our needs.

So let's get back to the mysterious Canvas class again. It is a pretty powerful class that wraps a custom low-level graphics library called Skia, specifically tailored to perform 2D rendering on the CPU. The Canvas class provides us with many drawing methods for various shapes, bitmaps, and even text.

Where do the draw methods draw to? That depends. A Canvas can render to a Bitmap instance; Bitmap is another class provided by the Android's 2D API, which we'll look into later on. In this case, it is drawing to the area on the screen that the View is taking up. Of course, this is an insane oversimplification. Under the hood, it will not directly draw to the screen, but to some sort of bitmap that the system will later use in combination with the bitmaps of all other Views of the activity to composite the final output image. That image will then be handed over to the GPU, which will display it on the screen through another set of mysterious paths.

We don't really need to care about the details. From our perspective, our View seems to stretch over the whole screen, so it may as well be drawing to the framebuffer of the system. For the rest of this discussion, we'll pretend that we directly draw to the framebuffer, with the system doing all the nifty things like vertical retrace and double-buffering for us.

The onDraw() method will be called as often as the system permits. For us, it is very similar to the body of our theoretical game main loop. If we were to implement a game with this method, we'd place all our game logic into this method. We won't do that for various reasons, though, performance being one of them.

So let's do something interesting. Every time I get access to a new drawing API, I write a little test that checks if the screen is really redrawn frequently. It's a sort of a poor man's light show. All I do in each call to the redraw method is fill the screen with a new random color. That way I only need to find the method of that API that allows me to fill the screen, without needing to know a lot about the nitty-gritty details. Let's write such a test with our own custom RenderView implementation.

The method of the Canvas to fill its rendering target with a specific color is called Canvas.drawRGB():

The r, g, and b arguments each stand for one component of the color we want to fill the "screen" with. Each of them has to be in the range 0 to 255, so we actually specify a color in the RGB888 format here. If you don't remember the details regarding colors, take a look at the "Encoding Colors Digitally" section of Chapter 3 again, as we'll be using that info throughout the rest of this chapter.

Listing 4-12 shows you the code for our little light show.

CAUTION: Running this code will rapidly fill the screen with a random color. If you have epilepsy or are otherwise light-sensitive in any way, don't run it.

Listing 4-12. The RenderViewTest Activity package com.badlogic.androidgames;

import java.util.Random;

import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.os.Bundle; import android.view.View; import android.view.Window; import android.view.WindowManager;

public class RenderViewTest extends Activity { class RenderView extends View { Random rand = new Random();

public RenderView(Context context) { super(context);

protected void onDraw(Canvas canvas) {

canvas.drawRGB(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256)); invalidate();

^Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,

WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(new RenderView(this));

For our first graphics demo, this is pretty concise. We define the RenderView class as an inner class of the RenderViewTest activity. The RenderView class derives from the View class, as discussed earlier, and has a mandatory constructor, as well as the overridden onDraw() method. It also has an instance of the Random class as a member; we'll use that to generate our random colors.

The onDraw() method is dead simple. We first tell the Canvas to fill the whole view with a random color. For each color component, we simply specify a random number between 0 and 255 (Random.nextInt() is exclusive). After that we tell the system that we want the onDraw() method to be called again as soon as possible.

The onCreate() method of the activity enables full-screen mode and sets an instance of our RenderView class as the content view. To keep the example short, we're leaving out the wake lock for now.

Taking a screenshot of this example is a little bit pointless. All it does is fill the screen with a random color as fast as the system allows on the UI thread. It's nothing to write home about. Let's do something more interesting instead: draw some shapes.

NOTE: While the preceding method of continuous rendering works, I strongly recommend not to use it! We should do as little work on the UI thread as possible. We'll discuss in a minute how to do it properly in a separate thread, where we can also implement our game logic later on.

0 -1

Post a comment