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.