package course.labs.graphicslab; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.media.AudioManager; import android.media.SoundPool; import android.media.SoundPool.OnLoadCompleteListener; import android.os.Bundle; import android.util.Log; import android.view.GestureDetector; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.widget.RelativeLayout; public class BubbleActivity extends Activity { // These variables are for testing purposes, do not modify private final static int RANDOM = 0; private final static int SINGLE = 1; private final static int STILL = 2; private static int speedMode = RANDOM; private static final String TAG = "Lab-Graphics"; // The Main view private RelativeLayout mFrame; // Bubble image's bitmap private Bitmap mBitmap; // Display dimensions private int mDisplayWidth, mDisplayHeight; // Sound variables // AudioManager private AudioManager mAudioManager; // SoundPool private SoundPool mSoundPool; // ID for the bubble popping sound private int mSoundID; // Audio volume private float mStreamVolume; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Set up user interface mFrame = (RelativeLayout) findViewById(R.id.frame); // Load basic bubble Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.b64); } @Override protected void onResume() { super.onResume(); // Manage bubble popping sound // Use AudioManager.STREAM_MUSIC as stream type mAudioManager = (AudioManager) getSystemService(AUDIO_SERVICE); mStreamVolume = (float) mAudioManager .getStreamVolume(AudioManager.STREAM_MUSIC) / mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); // TODO - make a new SoundPool, allowing up to 10 streams // TODO - load the sound from res/raw/bubble_pop.wav } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { // Get the size of the display so this View knows where borders are mDisplayWidth = mFrame.getWidth(); mDisplayHeight = mFrame.getHeight(); } } @Override protected void onPause() { mSoundPool.unload(mSoundID); mSoundPool.release(); mSoundPool = null; super.onPause(); } // BubbleView is a View that displays a bubble. // This class handles animating, drawing, and popping amongst other actions. // A new BubbleView is created for each bubble on the display public class BubbleView extends View { private static final int BITMAP_SIZE = 64; private static final int REFRESH_RATE = 40; private final Paint mPainter = new Paint(); private ScheduledFuture<?> mMoverFuture; private int mScaledBitmapWidth; private Bitmap mScaledBitmap; // location, speed and direction of the bubble private float mXPos, mYPos, mDx, mDy, mRadius, mRadiusSquared; private long mRotate, mDRotate; BubbleView(Context context, float x, float y) { super(context); Log.i(TAG, "Creating Bubble at: x:" + x + " y:" + y); // Create a new random number generator to // randomize size, rotation, speed and direction Random r = new Random(); // Creates the bubble bitmap for this BubbleView createScaledBitmap(r); // Radius of the Bitmap mRadius = mScaledBitmapWidth / 2; mRadiusSquared = mRadius * mRadius; // Adjust position to center the bubble under user's finger mXPos = x - mRadius; mYPos = y - mRadius; // Set the BubbleView's speed and direction setSpeedAndDirection(r); // Set the BubbleView's rotation setRotation(r); mPainter.setAntiAlias(true); } private void setRotation(Random r) { if (speedMode == RANDOM) { // TODO - set rotation in range [1..3] } else { mDRotate = 0; } } private void setSpeedAndDirection(Random r) { // Used by test cases switch (speedMode) { case SINGLE: mDx = 20; mDy = 20; break; case STILL: // No speed mDx = 0; mDy = 0; break; default: // TODO - Set movement direction and speed // Limit movement speed in the x and y // direction to [-3..3] pixels per movement. } } private void createScaledBitmap(Random r) { if (speedMode != RANDOM) { mScaledBitmapWidth = BITMAP_SIZE * 3; } else { // TODO - set scaled bitmap size in range [1..3] * BITMAP_SIZE } // TODO - create the scaled bitmap using size set above } // Start moving the BubbleView & updating the display private void start() { // Creates a WorkerThread ScheduledExecutorService executor = Executors .newScheduledThreadPool(1); // Execute the run() in Worker Thread every REFRESH_RATE // milliseconds // Save reference to this job in mMoverFuture mMoverFuture = executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { // TODO - implement movement logic. // Each time this method is run the BubbleView should // move one step. If the BubbleView exits the display, // stop the BubbleView's Worker Thread. // Otherwise, request that the BubbleView be redrawn. } }, 0, REFRESH_RATE, TimeUnit.MILLISECONDS); } // Cancel the Bubble's movement // Remove Bubble from mFrame // Play pop sound if the BubbleView was popped private void stop(final boolean wasPopped) { if (null != mMoverFuture) { if (!mMoverFuture.isDone()) { mMoverFuture.cancel(true); } // This work will be performed on the UI Thread mFrame.post(new Runnable() { @Override public void run() { // TODO - Remove the BubbleView from mFrame // TODO - If the bubble was popped by user, // play the popping sound Log.i(TAG, "Bubble removed from view!"); } }); } } // Draw the Bubble at its current location @Override protected synchronized void onDraw(Canvas canvas) { // TODO - save the canvas // TODO - increase the rotation of the original image by mDRotate // TODO Rotate the canvas by current rotation // Hint - Rotate around the bubble's center, not its position // TODO - draw the bitmap at it's new location // TODO - restore the canvas } // Returns true if the BubbleView is still on the screen after the move // operation private synchronized boolean moveWhileOnScreen() { // TODO - Move the BubbleView return isOutOfView(); } // Return true if the BubbleView is still on the screen after the move // operation private boolean isOutOfView() { // TODO - Return true if the BubbleView is still on the screen after // the move operation return true || false; } } @Override public void onBackPressed() { openOptionsMenu(); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO- Add cases to handle addition and deletion of bubbles // from options menu added bubbles should be given random locations. // The bubble to delete is the most recently added bubble that // is still in the frame. // Hint: You can get all Views in mFrame using the // ViewGroup.getChildCount() method switch (item.getItemId()) { case R.id.menu_still_mode: speedMode = STILL; return true; case R.id.menu_single_speed: speedMode = SINGLE; return true; case R.id.menu_random_mode: speedMode = RANDOM; return true; case R.id.quit: exitRequested(); return true; default: return super.onOptionsItemSelected(item); } } private void exitRequested() { super.onBackPressed(); } }