// BouncingBallActivity.java (c) Kari Laitinen // http://www.naturalprogramming.com // 2012-09-13 File created. // 2015-03-22 Latest modification. /* This program shows a ball, or a bouncer, that moves on the screen. The ball bounces when it hits a 'wall'. The bouncer will explode when it is touched. When the bouncer has fully exploded, it will be re-created if the screen is touched again. To run this program set up an Andoid Studio project and set BouncingBallActivity as the name of its main activity. Use the package name given below. Overwrite the automatically created .java file with this file. This program does not need any specific .xml files. This program provides the following classes which might be used also in other programs: GraphicalObject - represent an object that has a position, a color Bouncer extends GraphicalObject - represents a ball object that can move and bounce inside a given rectangular area RotatingBouncer extends Bouncer - represents a bouncer that rotates while it is moving ExplodingBouncer extends RotatingBouncer - a rotating bouncer that can be made to explode and disappear */ package bouncing.ball ; import android.app.Activity ; import android.os.Bundle ; import android.graphics.* ; // Classes Canvas, Color, Paint, RectF, etc. import android.view.View ; import android.view.MotionEvent ; import android.view.GestureDetector ; import android.content.Context ; import java.util.ArrayList ; class GraphicalObject { PointF object_center_point ; // object_velocity specifies the number of pixels the object // will be moved in a single movement operation. float object_velocity = 4.0F ; int object_color = Color.RED ; public PointF get_object_position() { return object_center_point ; } public void set_color( int new_color ) { object_color = new_color ; } public void move_right() { object_center_point.set( object_center_point.x + object_velocity, object_center_point.y ) ; } public void move_left() { object_center_point.set( object_center_point.x - object_velocity, object_center_point.y ) ; } public void move_up() { object_center_point.set( object_center_point.x, object_center_point.y - object_velocity); } public void move_down() { object_center_point.set( object_center_point.x, object_center_point.y + object_velocity); } public void move_this_object( float movement_in_direction_x, float movement_in_direction_y ) { object_center_point.set( object_center_point.x + movement_in_direction_x, object_center_point.y + movement_in_direction_y ) ; } public void move_to_position( float new_position_x, float new_position_y ) { object_center_point.set( new_position_x, new_position_y ) ; } } class Bouncer extends GraphicalObject { float bouncer_radius = 60 ; // bouncer_direction is an angle in radians. This angle specifies // the direction where the bouncer will be moved next. float bouncer_direction = (float) ( Math.random() * Math.PI * 2 ); RectF bouncing_area ; public Bouncer( PointF given_position, int given_color, RectF given_bouncing_area ) { object_center_point = given_position ; object_color = given_color ; bouncing_area = given_bouncing_area ; } public float get_bouncer_radius() { return bouncer_radius ; } public void shrink() { // The if-construct ensures that the ball does not become // too small. if ( bouncer_radius > 5 ) { bouncer_radius -= 3 ; } } public void enlarge() { bouncer_radius = bouncer_radius + 3 ; } public void set_radius( int new_radius ) { if ( new_radius > 3 ) { bouncer_radius = (float) new_radius ; } } public boolean contains_point( PointF given_point ) { // Here we use the Pythagorean theorem to calculate the distance // from the given point to the center point of the ball. // See the note at the end of this file. float distance_from_given_point_to_ball_center = (float) Math.sqrt( Math.pow( object_center_point.x - given_point.x, 2 ) + Math.pow( object_center_point.y - given_point.y, 2 ) ) ; return ( distance_from_given_point_to_ball_center <= bouncer_radius ) ; } // The move() method is supposed to be called something like // 25 times a second. public void move() { // In the following statement a minus sign is needed when the // y coordinate is calculated. The reason for this is that the // y direction in the graphical coordinate system is 'upside down'. object_center_point.set( object_center_point.x + object_velocity * (float)Math.cos(bouncer_direction), object_center_point.y - object_velocity * (float)Math.sin(bouncer_direction)); // Now, after we have moved this bouncer, we start finding out whether // or not it has hit a wall or some other obstacle. If a hit occurs, // a new direction for the bouncer must be calculated. // The following four if constructs must be four separate ifs. // If they are replaced with an if - else if - else if - else if // construct, the program will not work when the bouncer enters // a corner in an angle of 45 degrees (i.e. Math.PI / 4). if ( object_center_point.y - bouncer_radius <= bouncing_area.top ) { // The bouncer has hit the northern 'wall' of the bouncing area. bouncer_direction = 2 * (float) Math.PI - bouncer_direction ; } if ( object_center_point.x - bouncer_radius <= bouncing_area.left ) { // The western wall has been reached. bouncer_direction = (float) Math.PI - bouncer_direction ; } if ( object_center_point.y + bouncer_radius >= bouncing_area.bottom ) { // Southern wall has been reached. bouncer_direction = 2 * (float) Math.PI - bouncer_direction ; } if ( object_center_point.x + bouncer_radius >= bouncing_area.right ) { // Eastern wall reached. bouncer_direction = (float) Math.PI - bouncer_direction ; } } public void draw( Canvas canvas ) { Paint filling_paint = new Paint() ; filling_paint.setStyle( Paint.Style.FILL ) ; filling_paint.setColor( object_color ) ; Paint outline_paint = new Paint() ; outline_paint.setStyle( Paint.Style.STROKE ) ; // Default color for a Paint is black. canvas.drawCircle( object_center_point.x, object_center_point.y, bouncer_radius, filling_paint ) ; canvas.drawCircle( object_center_point.x, object_center_point.y, bouncer_radius, outline_paint ) ; } } class RotatingBouncer extends Bouncer { int current_rotation = 0 ; Paint another_ball_paint = new Paint() ; public RotatingBouncer( PointF given_position, int given_color, RectF given_bouncing_area ) { super( given_position, given_color, given_bouncing_area ) ; another_ball_paint.setColor( 0xFF007F00 ) ; // dark green } public void move() { super.move() ; // run the corresponding upper class method first current_rotation = current_rotation + 2 ; if ( current_rotation >= 360 ) { current_rotation = 0 ; } } public void draw( Canvas canvas ) { super.draw( canvas ) ; // run the upper class draw() first canvas.save() ; // Save the original canvas state // First we move the zero point of the coordinate system into // the center point of the ball. canvas.translate( object_center_point.x, object_center_point.y ) ; // Rotate the coordinate system as much as is the value of // the data field current_rotation. canvas.rotate( current_rotation ) ; // Fill one quarter of the ball with another color. canvas.drawArc( new RectF( -bouncer_radius, -bouncer_radius, bouncer_radius, bouncer_radius ), 0, 90, true, another_ball_paint ) ; // Fill another quarter of the ball with the new color. canvas.drawArc( new RectF( -bouncer_radius, -bouncer_radius, bouncer_radius, bouncer_radius ), 180, 90, true, another_ball_paint ) ; // Finally we restore the original coordinate system. canvas.restore() ; } } class ExplodingBouncer extends RotatingBouncer { static final int BALL_ALIVE_AND_WELL = 0 ; static final int BALL_EXPLODING = 1 ; static final int BALL_EXPLODED = 2 ; int ball_state = BALL_ALIVE_AND_WELL ; int explosion_color_alpha_value = 0 ; public ExplodingBouncer( PointF given_position, int given_color, RectF given_bouncing_area ) { super( given_position, given_color, given_bouncing_area ) ; } public void explode_ball() { ball_state = BALL_EXPLODING ; enlarge() ; // make the ball somewhat larger in explosion enlarge() ; } public boolean is_exploded() { return ( ball_state == BALL_EXPLODED ) ; } public void move() { // The ball will not move if it is exploding or exploded. if ( ball_state == BALL_ALIVE_AND_WELL ) { super.move() ; // move the ball with the superclass method } } public void draw( Canvas canvas ) { if ( ball_state == BALL_ALIVE_AND_WELL ) { super.draw( canvas ) ; // run the upper class draw() first } else if ( ball_state == BALL_EXPLODING ) { if ( explosion_color_alpha_value > 0xFF ) { ball_state = BALL_EXPLODED ; } else { // The ball will be 'exploded' by drawing a transparent // yellow ball over the original ball. // As the opaqueness of the yellow color gradually increases, // the ball becomes ultimately completely yellow in // the final stage of the explosion. super.draw( canvas ) ; // draw the original ball first Paint explosion_paint = new Paint() ; explosion_paint.setColor( Color.YELLOW ) ; explosion_paint.setAlpha( explosion_color_alpha_value ) ; explosion_paint.setStyle( Paint.Style.FILL ) ; canvas.drawCircle( object_center_point.x, object_center_point.y, bouncer_radius, explosion_paint ) ; explosion_color_alpha_value += 4 ; // decrease transparency } } } } class BouncingBallView extends View implements Runnable { Thread thread_that_moves_the_ball ; boolean thread_must_be_executed ; int view_width, view_height ; ExplodingBouncer ball_on_screen ; public BouncingBallView( Context context ) { super( context ) ; setBackgroundColor( 0xFFF0F8FF ) ; // AliceBlue, very light blue } public void onSizeChanged( int current_width_of_this_view, int current_height_of_this_view, int old_width_of_this_view, int old_height_of_this_view ) { view_width = current_width_of_this_view ; view_height = current_height_of_this_view ; RectF bouncing_area = new RectF( 0, 0, view_width, view_height ) ; ball_on_screen = new ExplodingBouncer( new PointF( view_width / 2, view_height / 2 ), Color.GREEN, bouncing_area) ; } public void start_animation_thread() { if ( thread_that_moves_the_ball == null ) { thread_must_be_executed = true ; thread_that_moves_the_ball = new Thread( this ) ; thread_that_moves_the_ball.start() ; } } public void stop_animation_thread() { if ( thread_that_moves_the_ball != null ) { thread_must_be_executed = false ; thread_that_moves_the_ball.interrupt() ; thread_that_moves_the_ball = null ; } } public void run() { while ( thread_must_be_executed == true ) { if ( ball_on_screen != null ) { ball_on_screen.move() ; } postInvalidate() ; try { Thread.sleep( 40 ) ; } catch ( InterruptedException caught_exception ) { // No actions to handle the exception. } } } public boolean onTouchEvent ( MotionEvent motion_event ) { if ( motion_event.getAction() == MotionEvent.ACTION_DOWN ) { // When the screen is touched we'll create a new ball if // the old ball has been exploded. if ( ball_on_screen.is_exploded() ) { RectF bouncing_area = new RectF( 0, 0, view_width, view_height ) ; ball_on_screen = new ExplodingBouncer( new PointF( view_width / 2, view_height / 2 ), Color.GREEN, bouncing_area) ; } else if ( ball_on_screen.contains_point( new PointF( motion_event.getX(), motion_event.getY() ) ) ) { ball_on_screen.explode_ball() ; } } return true ; } protected void onDraw( Canvas canvas ) { if ( ball_on_screen != null ) { ball_on_screen.draw( canvas ) ; } } } public class BouncingBallActivity extends Activity { BouncingBallView bouncing_ball_view; public void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ) ; bouncing_ball_view = new BouncingBallView( this ) ; setContentView( bouncing_ball_view ) ; } public void onStart() { super.onStart() ; bouncing_ball_view.start_animation_thread() ; } public void onStop() { super.onStop() ; bouncing_ball_view.stop_animation_thread() ; } }