Hour 6. Drawing Basic Shapes and Text


What You’ll Learn in This Hour

• How to draw shapes using Canvas

• How to print text using Canvas

• How to change draw settings with Paint

• How to write Javadoc code comments


In this hour you learn more about Canvas and View and about the many methods available for drawing shapes and text.

Drawing Basic Vector Shapes

As you saw in the previous hour, you can draw to the Android screen using Canvas and View. We take a look at most of the Canvas drawing methods here. Although you might not consider vector shapes useful, they can be handy for in-game editors, for highlighting game objects, for underlining or boxing areas, and even for simple games.

Drawing Circles

Because you have already seen a circle drawn in the previous hour, let’s start with circles again this hour before moving on to other shapes and techniques. We will learn about the features of the Paint class along the way. The Canvas.drawCircle() method is used to draw circles:

public void drawCircle (float cx, float cy, float radius, Paint paint)

Drawing Lines

The Canvas class has a method called drawLine() that will draw a single line, and a method called drawLines() that will draw several lines passed in an array. We will focus on the single line drawing method, which has this syntax:

public void drawLine (float startX, float startY, float stopX,
float stopY, Paint paint)

Drawing Boxes

Just for fun, let’s see how to derive our own box-drawing method by using Canvas.drawLine(). Call the new method drawBox():

public void drawBox( Canvas canvas, Point p1, Point p2, Paint paint ) {
    canvas.drawLine( p1.x, p1.y, p2.x, p1.y, paint);
    canvas.drawLine( p2.x, p1.y, p2.x, p2.y+1, paint);
    canvas.drawLine( p1.x, p1.y, p1.x, p2.y, paint);
    canvas.drawLine( p1.x, p2.y, p2.x+1, p2.y+1, paint);
}

To make the new method even more interesting, we can write some overloaded versions that take the two Point parameters in more convenient forms:

public void drawBox( Canvas canvas, float x1, float y1, float x2,
float y2, Paint paint ) {
    drawBox( canvas, (int)x1, (int)y1, (int)x2, (int)y2, paint );
}

public void drawBox( Canvas canvas, int x1, int y1, int x2, int y2,
Paint paint) {
    drawBox( canvas, new Point(x1,y1), new Point(x2,y2), paint );
}

There is just one problem: Canvas already provides a drawRect() method with several overloads already! Alas, we might as well use the built-in method instead. “Why bring it up, then?” you may ask.

There is a good lesson in this: Always study a new SDK or library before spending time writing your own methods that might already have been done. Many features in the Java namespaces will most likely solve any programming problem you’re likely to run into. Canvas has all the basic methods you will need to do vector graphics for various purposes.

One common need for vectors is to highlight game objects, such as units, in a real-time strategy (RTS) game, where you can use the mouse to drag a box around several units to select them. Here is Canvas.drawRect():

public void drawRect (float left, float top, float right, float bottom,
Paint paint)

Drawing Rounded Rectangles

A variation of the rectangle is also available in our trusty Canvas class, but this new version has rounded corners! We can draw this new shape using a method called Canvas.drawRoundRect():

public void drawRoundRect (RectF rect, float rx, float ry, Paint paint)

The float rx and float ry parameters specify the radius of the quarter-circle used to draw the rounded corners. This shape is very pleasing and may be preferable to a solid square rectangle in some cases. For instance, if you are making a graphical user interface (GUI) for your game, rounded rectangles look especially nice as buttons and window borders.

Drawing Triangles

We can derive more shapes with the basic Canvas.drawLine() method, such as triangles. This is a fairly easy shape to draw, although it requires three points rather than two, as was the case with drawBox() (which we might say is now deprecated, to borrow a popular Java term). Although the code listing in the previous hour contained the complete set of import statements you’ll need at this stage, we’re skipping ahead a bit without covering every single line of code here. Note that the usual import lines are still needed. In the case of the Canvas methods, be sure to use import android.graphics.*. All of the complete examples will have this line, so you may refer to the listings for reference.

public void drawTriangle( Canvas canvas, int x1,int y1, int x2,int y2,
int x3,int y3, Paint paint ) {
    drawTriangle(canvas, new Point(x1,y1), new Point(x2,y2),
        new Point(x3,y3), paint);
}

public void drawTriangle( Canvas canvas, Point p1, Point p2,
Point p3, Paint paint ) {
    canvas.drawLine(p1.x,p1.y,p2.x,p2.y, paint);
    canvas.drawLine(p2.x,p2.y,p3.x,p3.y, paint);
    canvas.drawLine(p3.x,p3.y,p1.x,p1.y, paint);
}

Changing the Style: Stroke and Fill

Some basic shapes can be drawn either as an outline or border (stroked) or filled in with a color (filled). The stroke constant is Style.STROKE, and the filled constant is Style.FILL. You can set the draw style with a Paint object. Paint.setStyle() and Paint.setColor() work in tandem to draw shapes with any color or draw style you specify. For instance, the following sets the draw color to green and style to filled:

paint.setColor( Color.GREEN );
paint.setStyle( Style.FILL );

Basic Graphics Demo

You have seen several shapes that can be drawn with the Canvas class, so what we need to do now is see them in action in a real example. See Figure 6.1.

Image

Figure 6.1. The Basic Graphics Demo shows off the Canvas drawing capabilities.

package android.program;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.*;
import java.util.Random;
import android.graphics.*;
import android.graphics.Paint.Style;
import android.view.*;

public class Main extends Activity  {

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(new DrawView(this));
    }

    public class DrawView extends View {
        Random rand = new Random();

        public DrawView(Context context) {
            super(context);
            DisplayMetrics metrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(metrics);
        }

        @Override public void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            float px, py;

            //create a paint object
            Paint paint = new Paint();
            paint.setAntiAlias( true );
            paint.setColor( Color.WHITE );

            //clear the screen with dark blue
            canvas.drawColor( Color.rgb(0,0,80) );

            //draw some boxes
            for (int n=0; n<100; n+=4) {
                drawBox( canvas, 10+n, 10+n, 220-n, 220-n, paint );
            }

            //draw a yellow triangle
            paint.setColor( Color.YELLOW );
            drawTriangle( canvas, 225,220, 325,20, 425,220, paint );

            //draw small boxes in the path of a circle
            float radius=100f;
            float loops=6.0f;
            for (double angle = 0.0; angle < 2*Math.PI*loops; angle += 0.15){
                px = (float)(110 + Math.cos(angle) * radius);
                py = (float)(330 + Math.sin(angle) * radius);
                radius -= 0.3f;
                paint.setColor( Color.rgb(rand.nextInt(256),
                    rand.nextInt(256), rand.nextInt(256)) );
                canvas.drawRect( px-4, py-4, px+4, py+4, paint );
            }

            //draw a circle outline
            paint.setColor( Color.CYAN );
            paint.setStyle( Style.STROKE );
            canvas.drawCircle( 325, 325, 100, paint );
            paint.setStyle( Style.FILL );
            canvas.drawRoundRect( new RectF(325-70,325-70,325+70,325+70),
                20, 20, paint );

            //draw a filled circle
            paint.setColor( Color.GREEN );
            paint.setStyle( Style.FILL );
            canvas.drawCircle( 325, 530, 100, paint );

            //draw a filled oval
            paint.setColor( Color.RED );
            canvas.drawOval(new RectF(60,450,140,630), paint);
        }

        public void drawBox( Canvas canvas, float x1, float y1, float x2,
        float y2, Paint paint ){
            drawBox( canvas, (int)x1, (int)y1, (int)x2, (int)y2, paint );
        }

        public void drawBox( Canvas canvas, int x1, int y1, int x2,
        int y2, Paint paint) {
            drawBox( canvas, new Point(x1,y1), new Point(x2,y2), paint );
        }

        public void drawBox( Canvas canvas, Point p1, Point p2,
        Paint paint ) {
            canvas.drawLine( p1.x, p1.y, p2.x, p1.y, paint);
            canvas.drawLine( p2.x, p1.y, p2.x, p2.y+1, paint);
            canvas.drawLine( p1.x, p1.y, p1.x, p2.y, paint);
            canvas.drawLine( p1.x, p2.y, p2.x+1, p2.y+1, paint);
        }

        public void drawTriangle( Canvas canvas, int x1, int y1, int x2,
        int y2, int x3, int y3, Paint paint ) {
            drawTriangle(canvas, new Point(x1,y1), new Point(x2,y2),
                new Point(x3,y3), paint);
        }

        public void drawTriangle( Canvas canvas, Point p1, Point p2,
        Point p3, Paint paint ) {
            canvas.drawLine(p1.x,p1.y,p2.x,p2.y, paint);
            canvas.drawLine(p2.x,p2.y,p3.x,p3.y, paint);
            canvas.drawLine(p3.x,p3.y,p1.x,p1.y, paint);
        }
    }
}

Drawing Text

Canvas can also draw text based on fonts. You can specify the text output style using a Paint object. Our base Canvas method for drawing text is Canvas.drawText(), with several overloads. Our base Paint method for setting the text size is Paint.setTextSize(), which accepts any font point size, typically from 8 point (tiny) to 72 points (huge). You can also use Paint.setColor() to set the color used to draw the text characters, in the same way that color affects the other Canvas graphics primitives.

Following is a short example of the text output features. In Figure 6.2, the output is shown in portrait mode, whereas in Figure 6.3, it is shown in landscape mode. This is a good example that you may want to consider. In portrait mode there appears to be plenty of room on the screen on the lower half. But, as you can clearly see, in landscape mode the text paragraph reaches down to the bottom of the screen. This is a problem for a game. Like the preferred full-screen setting with no title bar, some settings are better for a game. You’ll learn to get around this potential problem by disabling the screen autorotate feature in code.

Image

Figure 6.2. Demonstration of text output in portrait mode.

Image

Figure 6.3. Demonstration of text output in landscape mode.

package android.program;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.util.*;
import android.graphics.*;
import android.graphics.Paint.*;
import android.view.*;

public class Main extends Activity {

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(new DrawView(this));
    }

    public class DrawView extends View {

        public DrawView(Context context) {
            super(context);
            DisplayMetrics metrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(metrics);
        }

        @Override public void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            float px, py;

            //create a paint object
            Paint paint = new Paint();
            paint.setAntiAlias( true );
            paint.setColor( Color.WHITE );

            //clear the screen with dark red
              canvas.drawColor( Color.rgb(30,0,0) );

              paint.setTextSize(18);
              paint.setTextAlign(Align.LEFT);
              canvas.drawText("This is 18-point text aligned left", 10, 20,
                  paint);

              paint.setTextAlign(Align.CENTER);
              paint.setTextSize(24);
              canvas.drawText("This is 24-point text aligned center",
                  canvas.getWidth()/2, 60, paint);

              paint.setTextAlign(Align.RIGHT);
              paint.setTextSize(30);
              canvas.drawText("This is 30-point text aligned right",
                  canvas.getWidth(), 110, paint);

              paint.setStyle(Style.FILL);
              paint.setColor(Color.rgb(140, 160, 130));
              canvas.drawRect(new Rect(10,150,420,690), paint);

              String text =
              "One cannot choose but wonder. Will he ever return? It may " +
              "be that he swept back into the past, and fell among the " +
              "blood-drinking, hairy savages of the Age of Unpolished " +
              "Stone; into the abysses of the Cretaceous Sea; or among " +
              "the grotesque saurians, the huge reptilian brutes of the " +
              "Jurassic times. He may even now - if I may use the phrase " +
              "- be wandering on some plesiosaurus-haunted Oolitic coral " +
              "reef, or beside the lonely saline lakes of the Triassic " +
              "Age. Or did he go forward, into one of the nearer ages, " +
              "in which men are still men, but with the riddles of our " +
              "own time answered and its wearisome problems solved? Into " +
              "the manhood of the race: for I, for my own part cannot " +
              "think that these latter days of weak experiment, frag- " +
              "mentary theory, and mutual discord are indeed man's " +
              "culminating time! I say, for my own part. He, I know - for " +
              "the question had been discussed among us long before the " +
              "Time Machine was made - thought but cheerlessly of the " +
              "Advancement of Mankind, and saw in the growing pile of " +
              "civilization only a foolish heaping that must inevitably " +
              "fall back upon and destroy its makers in the end. If " +
              "that is so, it remains for us to live as though it were " +
              "not so. But to me the future is still black and blank " +
              "- is a vast ignorance, lit at a few casual places by the " +
              "memory of his story. And I have by me, for my comfort, " +
              "two strange white flowers - shrivelled now, and brown and " +
              "flat and brittle - to witness that even when mind and " +
              "strength had gone, gratitude and a mutual tenderness still " +
              "lived on in the heart of man. " +
              "  --from 'The Time Machine' by H.G. Wells ";

              paint.setTextAlign(Align.LEFT);
              paint.setTextSize(14);
              paint.setColor(Color.BLACK);
              int x=15,y=170;
              char c;
              String line="";

              for (int n=0; n<text.length(); n++) {
                     c = text.charAt(n);
                     if (c == ' ') {
                           canvas.drawText(line,x,y,paint);
                           line = "";
                           y += 18;
                     }
                     else line += c;
              }
        }
    }
}


Watch Out

Most of the examples in this book assume that they are running on fairly modern Android hardware with a high resolution. Older Android devices with a low-res screen will not render some of the examples properly. If you are developing a game for a low-resolution Android device, you may need to adjust the X,Y position of some of the graphics. For more information, see the upcoming section titled “Android Screen Densities and Resolutions.”


Writing Code for Javadoc

Let’s pause for a moment to discuss an important issue that will come up fairly often. The subject is code commenting. Have you ever looked at someone’s source code and found it completely indecipherable? Gobbledegook? Spaghetti code? That’s a bit of a problem if you need to make changes to it or if you’re trying to learn a new programming technique by studying that code.

When you write self-documented code, there is no need for separate docs. That’s the theory at least! Eclipse has a very nice feature that will help with writing self-documented code. If you have a method definition already written in your source code file, add a blank line above the method definition and type /** followed by Enter. This causes Eclipse to autogenerate a method comment with the parameters listed in Javadoc format. For instance, consider this method definition:

public void drawBox(Canvas canvas,Point p1, Point p2, Paint paint)

Now add a blank line above this drawBox() method and type /** as follows:

/**
public void drawBox(Canvas canvas,Point p1, Point p2, Paint paint)

When you press Enter after the /**, Eclipse autogenerates the following Javadoc code for you:

/**
 *
 * @param canvas
 * @param p1
 * @param p2
 * @param paint
 */

The first starred line above the first @param is where you type in a description for the method. Each @param should be filled in with detail. The amount of detail is up to you, but I suggest at least the data type. Here is the Javadoc comment filled in with detail along with the entire method:

/**
 * Draws a box
 * @param Canvas canvas
 * @param Point p1 - upper left corner
 * @param Point p2 - lower right corner
 * @param Paint paint - draw style and color
 */
public void drawBox( Canvas canvas, Point p1, Point p2,
Paint paint ) {
    canvas.drawLine( p1.x, p1.y, p2.x, p1.y, paint);
    canvas.drawLine( p2.x, p1.y, p2.x, p2.y+1, paint);
    canvas.drawLine( p1.x, p1.y, p1.x, p2.y, paint);
    canvas.drawLine( p1.x, p2.y, p2.x+1, p2.y+1, paint);
}

This may seem like a lot of extra work on your part. It is especially time consuming when you make changes to a method while refining and debugging your code. So, you may want to hold off on doing too much documenting until you are satisfied that a method is finished (or nearly so).

You will now see how all this extra work pays off while writing a game. Suppose we’re making a vector graphics game like one of the classic arcade games (Asteroids, perhaps). For such a game, you will likely need to call your drawBox() method quite a bit. In fact, this method could be used in a bitmap-based 2D game or even a 3D game—for instance, to highlight objects that the player has selected.


By the Way

Javadoc does not work well with method definitions that take up multiple lines, although we must wrap some long lines to fit properly on a printed page. In source files, you will want to leave all parameters on a single line for the Javadoc parser to work correctly. Otherwise, there will be odd artifacts in the scanned parameter list.


Use the mouse cursor in the Eclipse editor to “mouse over” a call to drawBox(). A Javadoc help pop-up appears, showing the details of the method! Pressing F2 at this point will cause the pop-up to solidify and give you some options (see Figure 6.4). One option is the @ Javadoc icon that will bring up the method definition in the Javadoc window for further reference. The parameter data types are also hyperlinked to their @ Javadoc references.

Image

Figure 6.4. Testing a self-documented method with the Javadoc feature in Eclipse.

This is obviously not a big concern for a small game, let alone one used for learning purposes (such as the examples in this book). But if you get into the habit of quickly using the autogenerate feature and further get into the habit of adding details about each new Method you write, you will write cleaner code with less opportunity for bugs to sneak in.

Android Screen Densities and Resolutions

Android programs do not actually draw using resolutions defined in pixels, the way lower-level graphics routines do. Android programs use a concept known as pixel density or more precisely, density-independent pixel (dp for short). This is because of the variety of devices built around the Android OS, with widely varying screen sizes and resolutions. It might take some adjustment if you are more accustomed to working with pixels under a 2D graphics API or SDK. It is helpful to think of the “DP” measurement in terms of how large an image appears on a typical PC monitor.

An Android device with a very high density LCD might have a resolution of 1280×800 on a tiny screen of only 3″×4″! That’s extremely high density, but it is possible. On such a screen, pixels have less meaning in terms of planning for a user interface or a game. Four generalized screen sizes are standard on the Android platform, as listed in Table 6.1.

Table 6.1. Android General Screen Sizes

Image

Because Google leaves it up to the Android device manufacturers to use any resolution they wish for a device without hindrance from the OS, there is no standard, only a generalization. This is a disadvantage for developers, but at least we can count on these general standards. If a device has a very peculiar screen resolution, a game will be scaled to fit (as a worst-case scenario). Table 6.2 lists the most common screen definitions.

Table 6.2. Android Screen Definitions

Image

By the Way

In the AVD Manager you can change the Skin property, which defines the size of the emulator window—and thus the screen resolution of the emulator.


We can detect the screen settings using a class called DisplayMetrics and the drawing Canvas object passed as a parameter to the onDraw() method. We are especially interested in the screen resolution (which comes from Canvas) and the screen density (from DisplayMetrics).

Because you will most likely want to test your game on several devices, it is recommended that you create a new AVD for each type of device you want to test and then start up whichever one you want to test. Using the AVD, it takes a lot of patience to put up with its slow speed of operation. If possible, you will want to develop and test your code on a real Android device via USB cable.

The two most common modes for Android Tablets (such as the Samsung Galaxy Tab or Toshiba Thrive) are WSVGA and WXGA800. Take a look at Figure 6.5 for the output from our Screen Resolution Demo program running in portrait orientation or mode, while Figure 6.6 shows the output in landscape mode. Note how easy it is to read the text when it’s presented in black over a white background. This is a good idea when you want to communicate information to a player.

Image

Figure 6.5. The Screen Resolution Demo displays the screen properties.

Image

Figure 6.6. The same program running in landscape orientation/mode.

The tendency for a programmer is often to display text and information in a light over dark color theme. I think this is because programmers and engineers think of the LCD hardware when writing code, and the pixels are naturally black when they’re turned off. So, we tend to think of on or lit as better than off or unlit. This is a geeky engineering thing! Any good designer will tell you to consider the end user. At any rate, I tend to display text more often in dark over light for the aforementioned aesthetic reasons, as well as this ancillary reason: Such figures are much more readable in a printed book.

package android.program;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.util.*;
import android.graphics.*;
import android.view.*;

public class MyProgram extends Activity  {

    @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 DrawView(this));
    }

    public class DrawView extends View {
        DisplayMetrics dm;

        public DrawView(Context context) {
            super(context);
            dm = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(dm);
        }

        @Override public void onDraw(Canvas canvas) {
            super.onDraw(canvas);

            //fill screen with white
            canvas.drawRGB(255,255,255);

            //set text format and color
            Paint paint = new Paint();
            paint.setColor(Color.BLACK);
            paint.setTextSize(30f);

            //get canvas resolution
            int width = canvas.getWidth();
            int height = canvas.getHeight();

            //print out screen details
            int x=10,y=30,step=36;
            canvas.drawText("Density: " + dm.density,x,y,paint);
            y+=step;
            canvas.drawText("Scaled density: " + dm.scaledDensity,x,y,paint);
            y+=step;
            canvas.drawText("Density dpi: " + dm.densityDpi,x,y,paint);
            y+=step;
            canvas.drawText("Width: " + width,x,y,paint); y+=step;
            canvas.drawText("Height: " + height,x,y,paint); y+=step;
            canvas.drawText("X dpi: " + dm.xdpi,x,y,paint); y+=step;
            canvas.drawText("Y dpi: " + dm.ydpi,x,y,paint); y+=step;
        }
    }
}

Summary

This hour provided a quick introduction to the graphics features of the Canvas, which you should now feel fairly comfortable using for basic graphics output. In the next few hours, you will learn more advanced features, such as bitmaps, back buffers, sprites, and animation. It’s also about time to make your first Android game, so that will be an important goal in the very next hour.

Q&A

Q. Screen resolutions are a real finicky problem for Android developers. What do you suggest as a solution to the problem of trying to support a dozen or more resolutions in your game code? Discuss possible solutions.

A. Answers will vary. One possible answer involves using a standard resolution for the game and then scaling the output to any final screen resolution.

Q. The Paint class allows you to change the size of text fonts. How might you adjust the font size so that it looks uniform on any resolution?

A. This question might use the same answer as the previous question, but the discussion should revolve around ways to adjust the font size dynamically.

Workshop

Quiz

1. Which class makes it possible to draw shapes on the Android screen?

2. What are the two most common landscape-oriented screen resolutions used by Android tablets?

3. Which class can provide the screen resolution when Canvas is not available (that is, while in some method other than onDraw())?

Answers

1. Canvas

2. WSVGA and WXGA800

3. DisplayMetrics

Activities

The difference between QVGA (240×360) and WXGA854 (480×854) is huge. Suppose you are targeting two Android devices with these two resolutions, and you have to support both with the same source code. Disregarding the preceding Q&A question, what class or method will you specifically use to solve this problem?

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

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