• No results found

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():

CHAPTER 4: Android for Game Developers

162

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

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.