// RandomExplosionsActivity.java Copyright (c) Kari Laitinen // http://www.naturalprogramming.com/ // 2016-03-18 File created. // 2016-03-24 Last modification. /* This app shows many animated explosions on the screen. This app demonstrates: - the use of a custom Sprite class - a SurfaceView in place of a View To run this program set up an Andoid Studio project and set RandomExplosionsActivity 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, but you need to copy the following file to a corresponding folder in your project. src/main/res/drawable/explosion_frames_72_87x87.png */ package random.explosions ; import android.app.Activity ; import android.os.Bundle ; import android.graphics.* ; // Classes Canvas, Color, Paint, RectF, etc. import android.view.* ; // Classes View, Display, WindowManager, etc. import android.content.Context ; import android.view.SurfaceHolder ; import android.view.SurfaceView; import java.util.ArrayList ; // Here a class named Sprite is defined. A Sprite is a sequence of // pieces of an image. Those pieces, frames, will be drawn one by one when the // draw() method is called. After all frames are drawn, the Sprite can // be considered 'dead', and cannot be used any more. class Sprite { static final int DELAYING_BEFORE_SHOW = 1 ; static final int SPRITE_IS_ALIVE = 2 ; static final int SPRITE_HAS_FINISHED = 3 ; Bitmap sprite_image ; int number_of_frames_to_show ; int sprite_center_point_x, sprite_center_point_y ; int current_frame_index = 0 ; int frame_width, frame_height ; int sprite_state = DELAYING_BEFORE_SHOW ; int number_of_frames_to_delay ; // The second parameter for the constructor tells how many frames, // pieces of an image, can be found inside the image that is given // as the first parameter. // The image must be such that its frames are horizontally organized. // With the last parameter, you can specify how many frames will be // delayed before the showing of the sprite begins. public Sprite( Bitmap given_sprite_image, int number_of_horizontal_frames, int given_sprite_center_point_x, // x-position on canvas int given_sprite_center_point_y, // y-position on canvas int given_number_of_frames_to_delay ) { sprite_image = given_sprite_image ; number_of_frames_to_show = number_of_horizontal_frames ; sprite_center_point_x = given_sprite_center_point_x ; sprite_center_point_y = given_sprite_center_point_y ; number_of_frames_to_delay = given_number_of_frames_to_delay ; frame_width = sprite_image.getWidth() / number_of_horizontal_frames ; frame_height = sprite_image.getHeight() ; // System.out.print( "\n " + frame_width + " x " + frame_height ) ; } public boolean sprite_has_finished() { return ( sprite_state == SPRITE_HAS_FINISHED ) ; } public void draw( Canvas canvas ) { if ( sprite_state == SPRITE_IS_ALIVE ) { // You can think that the parameters below first specify an image, // then they specify a rectangle within the image, and then they // specify a rectangle on the canvas. Image data is copied from the // rectangle inside the image to the rectangle on the canvas. canvas.drawBitmap( sprite_image, new Rect( frame_width * current_frame_index, 0, frame_width * ( current_frame_index + 1 ) - 1, frame_height - 1 ), new Rect( sprite_center_point_x - frame_width / 2, sprite_center_point_y - frame_height / 2, sprite_center_point_x + frame_width / 2, sprite_center_point_y + frame_height / 2 ), null ) ; current_frame_index ++ ; if ( current_frame_index >= number_of_frames_to_show ) { sprite_state = SPRITE_HAS_FINISHED ; } } else if ( sprite_state == DELAYING_BEFORE_SHOW ) { number_of_frames_to_delay -- ; if ( number_of_frames_to_delay <= 0 ) { sprite_state = SPRITE_IS_ALIVE ; } } } } final class RandomExplosionsView extends SurfaceView implements Runnable, SurfaceHolder.Callback { int view_width, view_height ; Thread animation_thread = null ; boolean thread_must_be_executed = false ; Paint background_paint = new Paint() ; Bitmap explosion_frames_image ; ArrayList explosions_to_show = new ArrayList() ; public RandomExplosionsView( Context context, int given_display_width, int given_display_height ) { super( context ) ; view_width = given_display_width ; view_height = given_display_height ; // Setting the background would probably hide the Surface. // setBackgroundColor( 0xFFFFF5EE ) ; // Light color background_paint.setStyle( Paint.Style.FILL ) ; background_paint.setColor( 0xFF2F4F4F) ; // With the BitmapFactory.Options we'll prevent scaling of the original image. BitmapFactory.Options bitmap_factory_options = new BitmapFactory.Options() ; bitmap_factory_options.inScaled = false ; explosion_frames_image = BitmapFactory.decodeResource( context.getResources(), R.drawable.explosion_frames_72_87x87, bitmap_factory_options ) ; getHolder().addCallback( this ) ; } private void create_random_explosions( int number_of_explosions_to_create ) { for ( int explosion_counter = 0 ; explosion_counter < number_of_explosions_to_create ; explosion_counter ++ ) { // With some 'mathematics' we'll ensure that the explosions do not take // place very close to the edges of the screen. int random_explosion_position_x = (int) ( Math.random() * ( view_width - 80 ) ) + 40 ; int random_explosion_position_y = (int) ( Math.random() * ( view_height - 80 ) ) + 40 ; int frames_to_delay_before_show = (int) ( Math.random() * 96 ) ; Sprite new_explosion = new Sprite( explosion_frames_image, 72, random_explosion_position_x, random_explosion_position_y, frames_to_delay_before_show ) ; explosions_to_show.add( new_explosion ) ; } } public void start_animation_thread() { if ( animation_thread == null ) { animation_thread = new Thread( this ) ; thread_must_be_executed = true ; animation_thread.start() ; } System.out.print( "\n Method start() executed. " ) ; } public void stop_animation_thread() { if ( animation_thread != null ) { animation_thread.interrupt() ; thread_must_be_executed = false ; animation_thread = null ; } System.out.print( "\n Method stop() executed. " ) ; } public void run() { System.out.print( "\n Method run() started." ) ; while ( thread_must_be_executed == true ) { try { Thread.sleep( 50 ) ; } catch ( InterruptedException caught_exception ) { thread_must_be_executed = false ; } // We'll check all the explosions if their sprites have finished // their animation. int explosion_index = 0 ; while ( explosion_index < explosions_to_show.size() ) { if ( explosions_to_show.get( explosion_index ).sprite_has_finished() ) { // With the remove() method we remove an object from the array. explosions_to_show.remove( explosion_index ) ; } else { explosion_index ++ ; } } // We'll limit the possible maximum number of explosions on the screen. if ( explosions_to_show.size() < 120 ) { create_random_explosions( 4 ) ; } // Next, we'll draw the explosions. Canvas canvas = getHolder().lockCanvas() ; if ( canvas != null ) { canvas.drawPaint( background_paint ) ; for ( Sprite explosion : explosions_to_show ) { explosion.draw( canvas ) ; } getHolder().unlockCanvasAndPost( canvas ) ; } } System.out.print( "\n Method run() stopped. " ) ; } public void surfaceChanged( SurfaceHolder holder, int format, int width, int height ) { System.out.print( "\n in SurfaceChanged()"); } public void surfaceCreated( SurfaceHolder holder ) { System.out.print( "\n in SurfaceCreated()"); start_animation_thread() ; } public void surfaceDestroyed( SurfaceHolder holder ) { System.out.print( "\n in SurfaceDestroyed()"); stop_animation_thread() ; } } public class RandomExplosionsActivity extends Activity { RandomExplosionsView random_explosions_view; public void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ) ; requestWindowFeature( Window.FEATURE_NO_TITLE ) ; getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ) ; Display display = ( (WindowManager) getSystemService( Context.WINDOW_SERVICE )).getDefaultDisplay() ; Point display_size = new Point() ; display.getSize( display_size ) ; random_explosions_view = new RandomExplosionsView( this, display_size.x, display_size.y ) ; setContentView( random_explosions_view ) ; } }