Notes from : http://code.tutsplus.com/tutorials/android-sdk-achieving-movement–mobile-5463
Movement.java
package com.jamesfroggatt.movement.app;
import android.app.Activity;
import android.os.Bundle;
public class Movement extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MovementView(this));
}
}
MovementView.java
package com.jamesfroggatt.movement.app;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
// our SurfaceView Object
// Notice the implementation of SurfaceHolder.Callback = This is what allows the
// view to detect things like the surface being created, changed or destroyed
public class MovementView extends SurfaceView implements SurfaceHolder.Callback {
private int xPos;
private int yPos;
private int xVel;
private int yVel;
private int width;
private int height;
private int circleRadius;
private Paint circlePaint;
UpdateThread updateThread;
// MovementView contructor:
// 1) Call the super() method to give us our SurfaceView to work with.
// 2) Link the class up with the SurfaceHolder.Callback
// 3) Initialize variables regarding the circle
// 4) Set the speed of movement in each direction
public MovementView(Context context) {
super(context);
getHolder().addCallback(this);
circleRadius = 20;
circlePaint = new Paint();
circlePaint.setColor(Color.BLUE);
xVel = 4;
yVel = 4;
}
// This is the function that will be called to draw the circle on each frame.
// It accomplishes two very simple tasks:
// 1) Repaint the Canvas all black to cover the previous frame.
// 2) Draw the new circle at the new set of coordinates.
@Override
protected void onDraw(Canvas canvas) {
// you can always experiment by removing this line if you wish!
canvas.drawColor(Color.WHITE);
canvas.drawCircle(xPos, yPos, circleRadius, circlePaint);
// canvas.drawCircle(xPos+10, yPos+10, circleRadius, circlePaint);
}
// This function is also called every frame and accomplishes two tasks:
// 1) handle the simple physics of the movement
// 2) Update the position of the ball and make it 'bounce' if it has hit the edge
// What may be confusing about this step is how the app determines which way to
// bounce the ball. The basic concept is that if the ball makes contact with the side,
// it reverses in the X direction. If the ball hits the top or bottom, it reverses
// in the Y direction. Also, it is important that when a collision is detected,
// we immediately set the ball to rest exactly on that wall, not behind it as
// it already might be. This prevents the ball getting stuck in a loop behind the wall.
public void updatePhysics() {
xPos += xVel;
yPos += yVel;
if (yPos - circleRadius < 0 || yPos + circleRadius > height) {
// the ball has hit the top or the bottom of the canvas
if (yPos - circleRadius < 0) {
// the ball has hit the top of the canvas
yPos = circleRadius;
} else {
// the ball has hit the bottom of the canvas
yPos = height - circleRadius;
}
// reverse the y direction of the ball
yVel *= -1;
}
if (xPos - circleRadius < 0 || xPos + circleRadius > width) {
// the ball has hit the sides of the canvas
if (xPos - circleRadius < 0) {
// the ball has hit the left of the canvas
xPos = circleRadius;
} else {
// the ball has hit the right of the canvas
xPos = width - circleRadius;
}
// reverse the x direction of the ball
xVel *= -1;
}
}
// This method is called when the workable surface area for the app is first created.
// You should note that it is never good practice to assume a screen size. Instead,
// use whatever means necessary to determine width/height information during the app
// runtime. This method accomplishes the following tasks:
// 1) Grab the rectangle bounding area of the canvas and pass the width
// and height to the respective variables.
// 2) Set the initial position of the ball to the top center of the screen.
// 3) Create the UpdateThread and start it.
// From the point that the UpdateThread begins running, the ball will begin to move:
public void surfaceCreated(SurfaceHolder holder) {
Rect surfaceFrame = holder.getSurfaceFrame();
width = surfaceFrame.width();
height = surfaceFrame.height();
xPos = width / 2;
yPos = circleRadius;
updateThread = new UpdateThread(this);
updateThread.setRunning(true);
updateThread.start();
}
// This method is not used in our app but is required by the SurfaceHolder.Callback
// implementation.
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
// This method deals with exiting the app and shutting down the thread. If we tried
// to shut down the app without dealing with the thread, the user would see a
// very annoying error dialog everytime they attempt to close the app.
// This small piece of code will let the thread finish the current task
// before closing, hence eliminating the error.
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
updateThread.setRunning(false);
while (retry) {
try {
updateThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}
UpdateThread.java
package com.jamesfroggatt.movement.app;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class UpdateThread extends Thread {
private long time;
private final int fps = 20;
private boolean toRun = false;
private MovementView movementView;
private SurfaceHolder surfaceHolder;
// UpdateThread constructor: The main purpose of this constructor is
// to populate the surfaceHolder variable which will eventually be
// used to provide a reference of the Canvas.
public UpdateThread(MovementView rMovementView) {
movementView = rMovementView;
surfaceHolder = movementView.getHolder();
}
// This method serves one simple, but essential purpose: to give the
// thread permission to run or not to run.
public void setRunning(boolean run) {
toRun = run;
}
// This is the main method of the Thread. The code in this method
// dictates what is done with each 'tick' of the thread.
// This is the list of tasks it performs:
// 1) Check if it has permission to run.
// 2) If so, check if the required time has passed to keep in line with the
// FPS (frames per second) value.
// 3) If so, set the canvas to empty.
// 4) Get a reference to the canvas and lock it to prepare for drawing.
// 5) Update the physics of the ball.
// 6) Draw the ball in the new position.
// 7) If it is safe to do so, lock and update the canvas.
@Override
public void run() {
Canvas c;
while (toRun) {
long cTime = System.currentTimeMillis();
if ((cTime - time) <= (1000 / fps)) {
c = null;
try {
c = surfaceHolder.lockCanvas(null);
movementView.updatePhysics();
movementView.onDraw(c);
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
time = cTime;
}
}
}