Android Programming Bouncing Game 2D
Android Programming Bouncing Game 2D
1. Custom 2D Graphics
Android support 2D graphics via its own library in packages android.graphics.drawable and
android.view.animation. Take note that Android does not support JDK's AWT and Swing.
[TODO] more
Create a project called "BouncingBall", with application name of "Bouncing Ball" and package
"com.example.bouncingball". Create a main activity called "MainActivity", with layout
"activity_main".
MainActivity.java
package com.example.bouncingball;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
Dissecting MainActivity.java
In method onCreate(), we construct an instance of our own custom View (called BouncingBallView
to be written next), and set it as the content-view of the MainActivity.
BouncingBallView.java
package com.example.bouncingball;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;
// Constructor
public BouncingBallView(Context context) {
super(context);
ballBounds = new RectF();
paint = new Paint();
}
// Update the position of the ball, including collision detection and reaction.
update();
// Delay
try {
Thread.sleep(30);
} catch (InterruptedException e) { }
// Called back when the view is first created or its size changes.
@Override
public void onSizeChanged(int w, int h, int oldW, int oldH) {
// Set the movement bounds for the ball
xMax = w-1;
yMax = h-1;
}
}
Dissecting BouncingBallView.java
To perform custom drawing, we create our own custom View class (called BouncingBallView) by
extending the android.view.View base class, and override the onDraw() method to program custom
rendering.
We first declare variables to keep track of the bounding box (xMin, xMax, yMin, yMax) and the ball (x, y
position, speed and radius).
In the overridden onDraw(), we use Canvas's drawOval(RectF bounds, Paint paint) to draw a
circle (for the ball). The drawOval() method takes two arguments - a RectF (rectangle in float) object
which specifies the bounds of the circle, and a Paint object carrying the paint properties such as the
color and the style. For the RectF object, we use RectF's set(float left, float top, float
right, float bottom) to specify its bounds with the current ball's position. For the Paint object, we
construct an instance with default settings, and set the drawing color via Paint's setColor(). We then
call the update() helper method to update the position of the ball, considering possible collision with
the bounding box. After a small delay (via Thread.sleep() to suspend the current thread), we invoke
View's invalidate() to inform the Android graphics sub-system to re-draw the view.
In the update() helper method, we move the ball in the x and y directions according to its speeds. We
then check for possible collision with the bounding box. If collision occurs, we adjust the position and
speed of the ball accordingly.
We also override the onSizeChanged(int w, int h, int oldW, int oldH) method, which is
called back when the View is first displayed and whenever the View's size changes. We set the
bounding box width and height according to the View's weight and height. Try Ctrl-F11 to change the
screen orientation.
Let's us include a status message to display the ball's center (x,y) and speed in the form of
"Ball@(x,y),Speed=(x,y)".
BouncingBallView.java
import java.util.Formatter;
import android.graphics.Typeface;
......
public class BouncingBallView extends View {
......
// Constructor
public BouncingBallView(Context context) {
super(context);
ballBounds = new RectF();
paint = new Paint();
// Set the font face and size of drawing text
paint.setTypeface(Typeface.MONOSPACE);
paint.setTextSize(16);
}
// Called back to draw the view. Also called after invalidate().
@Override
protected void onDraw(Canvas canvas) {
// Draw the ball
ballBounds.set(ballX-ballRadius, ballY-ballRadius, ballX+ballRadius,
ballY+ballRadius);
paint.setColor(Color.GREEN);
canvas.drawOval(ballBounds, paint);
// Update the position of the ball, including collision detection and reaction.
......
}
// Called back when the view is first created or its size changes.
@Override
public void onSizeChanged(int w, int h, int oldW, int oldH) { ...... }
}
Dissecting BouncingBallView.java
We allocate a StringBuilder as the buffer for our status message (which is more efficient than the
immutable String). In the onDraw(), we use Canvas's drawText() to draw the status message. We
use Paint's setTypeFace(), setTextSize() and setColor() to set the font face, size and color for
the text.
In update(), we write the ball's current position and speed to the status message.
Let's modify our program to enable key-control. The keyboard can generate two main events:
The View processes these events via the onKeyUp() an onKeyDown() event handlers:
// In android.view.View
// Called back when a key is pressed down/up
public boolean onKeyDown (int keyCode, KeyEvent event)
public boolean onKeyUp (int keyCode, KeyEvent event)
We shall use the DPAD's left, right, up, down key to increase the speed in the respective direction;
center key to halt the ball; keys 'A' and 'Z' to increase or decrease the radius of the ball.
To enable DPAD for the emulator: Select a device with DPAD; or clone an existing device with DPAD
by starting the "AVD Manager" ⇒ Select "Device Definitions" ⇒ Double-click to edit the device that
you have chosen for your emulator ⇒ In "Input", check "DPAD".
BouncingBallView.java
Modified as follows:
......
import android.view.KeyEvent;
......
Dissecting BouncingBallView.java
To process the key event, we override the onKeyUp() or onKeyDown() methods of the View class. We
adjust the ball's parameters based on the key inputs. Take note that to enable key inputs, we need to set
the View to be focusable, and request for the focus.
Let's modify our program to enable touch-control. The touch screen can generate events such as
"touch-down", "touch-up" and "touch-drag".
The android.view.View class processes these events via the onTouchEvent() event handlers:
// In android.view.View
// Call back when screen is touched
public boolean onTouchEvent (MotionEvent event)
Alternatively, you can use setOnTouchListener() to register a listener object that implements the
OnTouchListener interface and override the onTouch() method.
BouncingBallView.java
......
import android.view.MotionEvent;
......
// Touch-input handler
@Override
public boolean onTouchEvent(MotionEvent event) {
float currentX = event.getX();
float currentY = event.getY();
float deltaX, deltaY;
float scalingFactor = 5.0f / ((xMax > yMax) ? yMax : xMax);
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
// Modify rotational angles according to movement
deltaX = currentX - previousX;
deltaY = currentY - previousY;
ballSpeedX += deltaX * scalingFactor;
ballSpeedY += deltaY * scalingFactor;
}
// Save current x, y
previousX = currentX;
previousY = currentY;
return true; // Event handled
}
}
To enable and handle touch input, we override the View.onTouchEvent() to provide the touch-event
handling codes. We need to invoke View.setFocusableInTouchMode(true) to enable touch inputs.
Let's separate the ball, box and status message from the View to their own classes, as illustrated in the
following class diagram:
Box.java
The Box class represents the rectangular bounding box. We maintain the bounds (xMin, xMax, yMin,
and yMax) as int instance variables with package access. The set() method is to be used to set its
bounds. set() takes four arguments: top-left x and y, width and height for safer operation. The draw()
method render this shape via Canvas.drawRect().
package com.example.bouncingball;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
Ball.java
We keep the ball's center (x, y), speed and radius as instance variables with package access. We choose
float for these variables as it may involve in mathematically operations. The method
moveWithCollisionDetection() moves the ball (according to its speed), and adjusts the position and
speed of the ball if collision occurs. The method draw() renders this shape via Canvas.drawOval()
method.
package com.example.bouncingball;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
// Constructor
public Ball(int color) {
bounds = new RectF();
paint = new Paint();
paint.setColor(color);
}
StatusMessage.java
The StatusMessage class maintains the message in a StringBuilder (for efficiency). It has a
update() method, which takes the ball as argument, and update the ball's position and speed in the
buffer. The draw() method renders the message via Canvas.drawText().
package com.example.bouncingball;
import java.util.Formatter;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
BouncingBallView.java
The View class is greatly simplified after removing the ball, box and status message. It contains only
the overridden methods. It constructs a Box, a Ball, a StatusMessage in its constructor. In the
onDraw(), it draws the ball, box, status message, perform the updates, and invalidate() the view to
force a re-draw. The onSizeChange() sets up the Box's bounds. The onKeyUp() process the key inputs
and changes the Ball's parameters such as speed and radius.
package com.example.bouncingball;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.KeyEvent;
import android.view.View;
// Constructor
public BouncingBallView(Context context) {
super(context);
// To enable keypad
this.setFocusable(true);
this.requestFocus();
// To enable touch mode
this.setFocusableInTouchMode(true);
}
// Update the position of the ball, including collision detection and reaction.
ball.moveWithCollisionDetection(box);
statusMsg.update(ball);
// Delay
try {
Thread.sleep(30);
} catch (InterruptedException e) { }
// Called back when the view is first created or its size changes.
@Override
public void onSizeChanged(int w, int h, int oldW, int oldH) {
// Set the movement bounds for the ball
box.set(0, 0, w, h);
}
// Touch-input handler
@Override
public boolean onTouchEvent(MotionEvent event) {
float currentX = event.getX();
float currentY = event.getY();
float deltaX, deltaY;
float scalingFactor = 5.0f / ((box.xMax > box.yMax) ? box.yMax : box.xMax);
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
// Modify rotational angles according to movement
deltaX = currentX - previousX;
deltaY = currentY - previousY;
ball.speedX += deltaX * scalingFactor;
ball.speedY += deltaY * scalingFactor;
}
// Save current x, y
previousX = currentX;
previousY = currentY;
return true; // Event handled
}
}
MainActivity.java
No change.
RUN APP