Android Logo MathCS.org - Android

Drawing with a View

java -> android -> drawing with View ...

In this section we will create some custom drawing using the View class. This would be appropriate to create custom-looking views that are static. The could for example include a bitmap image, possibly enhanced by some custom drawing elements. In principle you can even use the View class for animations and games as well, but the class has a lot of overhead and is not suitable for smooth, high frame-rate games.

To create simple custom drawings:

  1. create a class that extends the View class
  2. load any images in the constructor
  3. overwrite the onDraw method and implement your drawing primitives

Of course we need to embed everything in an Activity, so we need to create at least two classes: one Activity and one View. The view class can be laid out with a layout manager without difficulty, but for simplicity we have the view occupy the entire activity.

First, here is the main class (which needs the View class below to compile):

public class Game extends Activity 
{
    private GameArena canvas;
	
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
    
        canvas = new GameArena(this);
        setContentView(canvas);
    }
}	

Next is the GameArena class. It uses a Context as constructor and contains, for now, no methods:

public class GameArena extends View
{
   public GameArena(Context context)
   {
      super(context);
   }
}

This will compile and run, but of course show just a blank screen with a title, as usual. Since we prefer to  have our game, eventually, take up the entire screen, we request to remove the title in the main activity by adding:

 requestWindowFeature(Window.FEATURE_NO_TITLE);

To the view class we add two fields containing Paint objects which encapsulates properties of a "paint brush". We set those properties in the constructor, override the onDraw method, and call a few drawing primitives with our new paint brushes. We also set the background drawing color to white with a call to canvas.setColor.

 public void onDraw(Canvas canvas) 
{
   private Paint paintBrushBlue = null;
   private Paint paintBrushGreen = null;

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

      paintBrushBlue = new Paint();
      paintBrushBlue.setColor(Color.BLUE);

      paintBrushGreen = new Paint();
      paintBrushGreen.setColor(Color.GREEN);
   }

   public void onDraw(Canvas canvas) 
   {
      canvas.drawColor(Color.WHITE);
      canvas.drawCircle(100, 100, 50, paintBrushBlue);
      canvas.drawCircle(100, 100, 25, paintBrushGreen);
   }
}

This will draw two circles, one inside each other, but they look kind of ragged (see picture below on the left). To smoothen them out, we add some additional properties to our paint brushes when they are constructed:

paintBrushBlue.setAntiAlias(true);
paintBrushBlue.setDither(true);
paintBrushGreen.setAntiAlias(true);
paintBrushGreen.setDither(true);

Now the circles are drawn smoothly, as shown in the picture below on the right.

circles without anti-alias circles with anti-alias
without anti-alias
with anti-alias and dithering

To simplify our code and to properly compartmentalize everything, we will create a MyCircle class that encapsulates our double-circle of radius 50 so that our main classes can focus on placing and later manipulating the object, while the exact look of our circle is handled by our new class. The new class looks as follows:

public class MyCircle
{
   private int x;
   private int y;
   private int radius;

   private Paint paintBrushBlue = null;
   private Paint paintBrushGreen = null;

   public MyCircle(int x, int y)
   {
      super();

      this.x = x;
      this.y = y;
      this.radius = 50;

      paintBrushBlue = new Paint();
      paintBrushBlue.setColor(Color.BLUE);

      paintBrushGreen = new Paint();
      paintBrushGreen.setColor(Color.GREEN);

      paintBrushBlue.setAntiAlias(true);
      paintBrushBlue.setDither(true);
      paintBrushGreen.setAntiAlias(true);
      paintBrushGreen.setDither(true);
   }

   public void draw(Canvas canvas)
   {
      canvas.drawCircle(x, y, radius, paintBrushBlue);
      canvas.drawCircle(x, y, radius/2, paintBrushGreen);
   }
}

Our previous View class can now be much simplified. It simply instantiates a new MyCircle object and gets it to draw itself in the onDraw method:

public class GameArena extends View
{
   private MyCircle circle = null;

   public GameArena(Context context)
   {
      super(context);
      circle = new MyCircle(50, 50);
   }
    public void onDraw(Canvas canvas) 
   {
      canvas.drawColor(Color.WHITE);
      circle.draw(canvas);
   }
}

We could now replace our MyCircle class with a more sophisticated drawing or even a pre-designed bitmap without having to change the min classes. Below, for example, is the code that uses a bitmap of one of the standard Android icons instead of our custom circle. We could easily create our own bitmap and use it instead, but for simplicity we'll use a predefined Android drawing. Not that we need to do a little bit of math to make sure that x and y still represent the center of the drawing. We also need to pass a reference to a View into the object so that it can load the necessary resources.

public class MyAlternateDrawing
{
   private Bitmap image = null;
   private int x = 0;
   private int y = 0;

   public MyAlternateDrawing(View view, int x, int y)
   {
      super();
      this.image = BitmapFactory.decodeResource(view.getResources(), R.drawable.icon); 
      this.x = x - image.getWidth()/2;
      this.y = y - image.getHeight()/2;
   }

   public void draw(Canvas canvas)
   {
      canvas.drawBitmap(image, x, y, null);
   }
}

The GameArena could now draw both objects easily, and we could fine-tune the appearance of the objects by modifying the individual classes as necessary.

drawing app with several objectsHomework: Modify the appropriate classes to display our circle object in the center of your app and the icon should appear in each of the four corners as shown in the picture on the right.

Important Hint: It is easy to display these objects at some fixed coordinates (you should have no trouble getting a circle to appear at (50, 50), say, and copies of the icon at various fixed positions of your app). The trouble is that you need to know the dimensions of the drawing area to position the circle right in the middle, but the dimensions are not yet available in the constructor of the GameArena. They are only available after the components have ben placed on the screen. Therefore you can not properly initialize the positions of the objects to draw in the constructor. As one posible solution, add a "setCenterTo(int x, int y)" method to both of the drawing classes, compute their location in the onDraw method of the GameArena, and then call on the setCenter method prior to drawing the objects. Note that  this should work but is not necessarily ideal, because now the drawing coordinates are redrawn every time the onDraw method executes, but really we should only recompute the coordinates if the dimensions of our app change. Still, it will be good enough for now.

Next we will see how to use a thread to animate our circle.