< CS193A Android Programming

Monkey View, Gestures, Game Animation

1. Click

2. Move (drag gesture)

3. Motoring (game animation)

4. Mid-animation Collision Detection

Monkey View Code

The view code is here, and the whole .zip project is linked below.

package edu.stanford.nick;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.View;

// A custom view that demonstrates custom drawing, clicking, dragging, and animation
// drawing with a timer -- techniques often needed for a game.

public class MonkeyView extends View {
	public MonkeyView(Context context) {
		super(context);

		// One easy way to have a background for the whole view.
		// background.jpg is vertical, so it looks right vertically.
		// We'll need a landscape version for when the phone is flipped.
		// (this can also be done in XML with android:background="@drawable/background")
		setBackgroundResource(R.drawable.background);

		mMonkey = makeScaled(BitmapFactory.decodeResource(context.getResources(), R.drawable.monkey), 150);
		mAlternate = makeScaled(BitmapFactory.decodeResource(context.getResources(), R.drawable.alternate), 150);

		// Start the monkey off at 50,50
		mX = 50;
		mY = 50;
	}

	/** Utility makes a scaled version of the given bitmap to the given width,
	 but keeping the original proportions. */
	private Bitmap makeScaled(Bitmap base, float width) {
		float scale = width / base.getWidth();
		return Bitmap.createScaledBitmap(base, (int)(base.getWidth()*scale), (int)(base.getHeight()*scale), false);

	}

	// Images used -- set in constructor
	private Bitmap mMonkey;
	private Bitmap mAlternate;  // alternate image to use when clicked

	// Monkey upper left
	private float mX;
	private float mY;

	// During click/drag
	private boolean mDragging;
	private float mLastX;
	private float mLastY;

	// For "motoring" -- animate monkey moving on its own by timer
	private boolean mMotoring;
	private float mDX;  // pixels per second
	private float mDY;

	// When clicked upon during motoring .. change to alternate image
	private boolean mClicked;
	private long mClickTime;


	// Draw ourselves
	public void onDraw(Canvas canvas) {                		
		if (!mClicked) {
			canvas.drawBitmap(mMonkey, mX, mY, null);
		}
		else {
			canvas.drawBitmap(mAlternate, mX, mY, null);
		}
	}


	// Notification of touch events on the view
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		float x = event.getX();
		float y = event.getY();
		int action = event.getAction();

		// Not motoring, just sitting there -- several cases.
		if (!mMotoring) {

			// ACTION_DOWN = click down (i.e. not a drag) -- 2 cases
			if (action == MotionEvent.ACTION_DOWN)  {
				// 1. On the monkey, set state to begin dragging
				if (mX <= x && x < mX + mMonkey.getWidth() &&
						mY <= y && y < mY + mMonkey.getHeight()) {

					mLastX = x;
					mLastY = y;
					mDragging = true;
				}
				else {
					// 2. Out in space -- move the monkey to there
					mX = x - mMonkey.getWidth() / 2;
					mY = y - mMonkey.getHeight() / 2;
					invalidate();

					// Width adjustment so monkey appears centered where the user clicks.
					// Recall the pattern: change model, then invalidate.
				}
			}

			// ACTION_MOVE = during a finger drag
			// Compute delta of gesture, apply that to the model.
			else if (action == MotionEvent.ACTION_MOVE && mDragging) {
				// Compute delta
				float deltaX = x - mLastX;
				float deltaY = y - mLastY;

				// Apply that delta to the monkey
				mX += deltaX;
				mY += deltaY;

				// Request a redraw, of course
				invalidate();

				// Store lastX/lastY for next time.
				mLastX = x;
				mLastY = y;
				
				// todo: could use invalidate(rect) variant to invalidate just
				// the area that needs a redraw -- good animation inner-loop speedup.
			}
		}  // !mMoving case


		else {
			// Monkey is motoring. Detect click on monkey -- set mClicked in that case.
			if (action == MotionEvent.ACTION_DOWN) {
				if (mX <= x && x < mX + mMonkey.getWidth() &&
						mY <= y && y < mY + mMonkey.getHeight()) {

					mClickTime = System.currentTimeMillis();
					mClicked = true;
					invalidate();
					// invalidate() here to follow the change-invalidate pattern
					// (mClicked in this case), although here
					// we're drawing 50 times a second anyway, so
					// it makes little difference.
				}
			}
		}


		// true = we handled this event (and we want future notifications)
		return true;
	}

	// How motoring works.
	// "timer" strategy -- a bit of code that runs 50x a second,
	// updates game state each time run() executes.

	private long mUpdateTime;  // time of last run
	
	// Code we'll have run 50x a second.
	private Runnable mUpdateState = new Runnable() {
		public void run() {

			// Running 50x per second is approximate -- fit the computation
			// to the exact elapsed time.
			long now = System.currentTimeMillis();
			float elapsed = (now - mUpdateTime)/1000.0f;
			mUpdateTime = now;

			mX += mDX * elapsed;  // mDX is pixels per second, so scale to elapsed time
			mY += mDY * elapsed;

			wrapAround();
			invalidate();

			// Let mClicked be true for 1 second.
			if (mClicked) {
				if (now - mClickTime >= 1000) {
					mClicked = false;
				}
			}

			// We post a pointer to ourselves to run again in 20ms
			getHandler().postDelayed(this, 20);
		}
	};

	/** Wraps the image from one edge to another.
	 Might look better using the center instead of the upper left.
	 */
	public void wrapAround() {
		if (mX < 0) mX += getWidth();
		if (mX >= getWidth()) mX -= getWidth();

		if (mY < 0) mY += getHeight();
		if (mY >= getHeight()) mY -= getHeight();
	}

	/** Randomizes dx/dy */
	public void randomizeDirection() {
		mDX = (float)(Math.random() * 600 - 300);
		mDY = (float)(Math.random() * 600 - 300);
	}

	/** Starts or restarts the motoring process. */
	public void startMotoring() {
		mUpdateTime = System.currentTimeMillis();
		randomizeDirection();
		getHandler().removeCallbacks(mUpdateState);  // Here being cautious -- avoid proliferating runnables
		getHandler().post(mUpdateState);
		mMotoring = true;
	}

	/** Stops the motoring process. */
	public void stopMotoring() {
		getHandler().removeCallbacks(mUpdateState);
		mMotoring = false;
	}

}


MonkeyImage.zip project -- to play with this example project, unzip it to your filesystem, then in Eclipse use File > Import > General > Existing Projects Into Workspace

Custom View "Duck" homework -- ducks in a view: Create a custom view and activity, like the monkey view. The view should have some image (e.g. a duck) which continuously moves horizontally acorss the view from left to right at some speed. When the image disappears off the right, it should appear at a random height at the left, and go across again, like targets in a shooting gallery. A click on the image should do something -- change the image or behavior or something. You are free to create your own details. You do not need to deal with pause/kill correctly. This homework is due by midnight Nov 18th (before thanksgiving). Please email Madiha if you need an extension.