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; } } }