<resources><stringname="app_name">SignaturePad</string><stringname="agreement">I agree to the terms and conditions.</string><stringname="clear_pad">Clear Pad</string><stringname="save_signature">Save Signature</string></resources>
Step 3: create a Values XML file name as dimens.xml
Right clicked on App> New> XML > Values XML file
<?xml version="1.0" encoding="utf-8"?><!-- Default screen margins, per the Android Design guidelines. --><resources><dimenname="activity_horizontal_margin">16dp</dimen><dimenname="activity_vertical_margin">16dp</dimen></resources>
Step 4: create a layout file for activity_main.xml
For a quick start, you may copy and paste the XML codes onto activity_main.xml
For a quick start, you may copy and paste the java codes onto MainActivity.java
package signature.mdad.signaturepad;importandroid.Manifest;importandroid.app.Activity;importandroid.content.Intent;importandroid.content.pm.PackageManager;importandroid.graphics.Bitmap;importandroid.graphics.Canvas;importandroid.graphics.Color;importandroid.net.Uri;importandroid.os.Bundle;importandroid.os.Environment;importandroid.support.annotation.NonNull;importandroid.support.v4.app.ActivityCompat;importandroid.util.Log;importandroid.view.View;importandroid.widget.Button;importandroid.widget.Toast;importcom.github.gcacace.signaturepad.views.SignaturePad;importjava.io.File;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.OutputStream;importjava.io.OutputStreamWriter;publicclassMainActivityextends Activity {privatestaticfinalint REQUEST_EXTERNAL_STORAGE =1;privatestatic String[] PERMISSIONS_STORAGE ={Manifest.permission.WRITE_EXTERNAL_STORAGE};private SignaturePad mSignaturePad;private Button mClearButton;private Button mSaveButton;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);
verifyStoragePermissions(this);
setContentView(R.layout.activity_main);
mSignaturePad =(SignaturePad) findViewById(R.id.signature_pad);
mSignaturePad.setOnSignedListener(new SignaturePad.OnSignedListener(){@OverridepublicvoidonStartSigning(){
Toast.makeText(MainActivity.this,"OnStartSigning", Toast.LENGTH_SHORT).show();}@OverridepublicvoidonSigned(){
mSaveButton.setEnabled(true);
mClearButton.setEnabled(true);}@OverridepublicvoidonClear(){
mSaveButton.setEnabled(false);
mClearButton.setEnabled(false);}});
mClearButton =(Button) findViewById(R.id.clear_button);
mSaveButton =(Button) findViewById(R.id.save_button);
mClearButton.setOnClickListener(new View.OnClickListener(){@OverridepublicvoidonClick(View view){
mSignaturePad.clear();}});
mSaveButton.setOnClickListener(new View.OnClickListener(){@OverridepublicvoidonClick(View view){
Bitmap signatureBitmap = mSignaturePad.getSignatureBitmap();if(addJpgSignatureToGallery(signatureBitmap)){
Toast.makeText(MainActivity.this,"Signature saved into the Gallery", Toast.LENGTH_SHORT).show();}else{
Toast.makeText(MainActivity.this,"Unable to store the signature", Toast.LENGTH_SHORT).show();}if(addSvgSignatureToGallery(mSignaturePad.getSignatureSvg())){
Toast.makeText(MainActivity.this,"SVG Signature saved into the Gallery", Toast.LENGTH_SHORT).show();}else{
Toast.makeText(MainActivity.this,"Unable to store the SVG signature", Toast.LENGTH_SHORT).show();}}});}@OverridepublicvoidonRequestPermissionsResult(int requestCode,@NonNull String permissions[],@NonNullint[] grantResults){switch(requestCode){caseREQUEST_EXTERNAL_STORAGE:{// If request is cancelled, the result arrays are empty.if(grantResults.length<=0|| grantResults[0]!= PackageManager.PERMISSION_GRANTED){
Toast.makeText(MainActivity.this,"Cannot write images to external storage", Toast.LENGTH_SHORT).show();}}}}public File getAlbumStorageDir(String albumName){// Get the directory for the user's public pictures directory.
File file =new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), albumName);if(!file.mkdirs()){
Log.e("SignaturePad","Directory not created");}return file;}publicvoidsaveBitmapToJPG(Bitmap bitmap, File photo)throws IOException {
Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas =new Canvas(newBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bitmap,0,0,null);
OutputStream stream =new FileOutputStream(photo);
newBitmap.compress(Bitmap.CompressFormat.JPEG,80, stream);
stream.close();}publicbooleanaddJpgSignatureToGallery(Bitmap signature){boolean result =false;try{
File photo =new File(getAlbumStorageDir("SignaturePad"), String.format("Signature_%d.jpg", System.currentTimeMillis()));
saveBitmapToJPG(signature, photo);
scanMediaFile(photo);
result =true;}catch(IOException e){
e.printStackTrace();}return result;}privatevoidscanMediaFile(File photo){
Intent mediaScanIntent =new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(photo);
mediaScanIntent.setData(contentUri);
MainActivity.this.sendBroadcast(mediaScanIntent);}publicbooleanaddSvgSignatureToGallery(String signatureSvg){boolean result =false;try{
File svgFile =new File(getAlbumStorageDir("SignaturePad"), String.format("Signature_%d.svg", System.currentTimeMillis()));
OutputStream stream =new FileOutputStream(svgFile);
OutputStreamWriter writer =new OutputStreamWriter(stream);
writer.write(signatureSvg);
writer.close();
stream.flush();
stream.close();
scanMediaFile(svgFile);
result =true;}catch(IOException e){
e.printStackTrace();}return result;}/** * Checks if the app has permission to write to device storage * <p/> * If the app does not has permission then the user will be prompted to grant permissions * * @param activity the activity from which permissions are checked */publicstaticvoidverifyStoragePermissions(Activity activity){// Check if we have write permissionint permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);if(permission != PackageManager.PERMISSION_GRANTED){// We don't have permission so prompt the user
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);}}}
4. Create new folder raw
in res folder and drag the 3 sound (.wav) files into it. The sound files can be downloaded here
5.Create a
new Java class CannonModel, in the same package, as shown: (this is the class
to create lines and other shapes). Fill in the missing codes (non-italics portion)
papackagemdad.game;importandroid.graphics.Point;publicclassCannonModel{public Point start;// starting Pointpublic Point end;// ending Point// default constructor initializes Points to (0, 0)publicCannonModel(){start =new Point(0,0);// start Point end =new Point(0,0);// end Point}// end constructor}// end class
6. Create a new class called CannonView (same package name).
papackagemdad.game;importjava.util.HashMap;importjava.util.Map;importandroid.app.Activity;importandroid.app.AlertDialog;importandroid.content.Context;importandroid.content.DialogInterface;importandroid.graphics.Canvas;importandroid.graphics.Color;importandroid.graphics.Paint;importandroid.graphics.Point;importandroid.graphics.Rect;importandroid.media.AudioManager;importandroid.media.SoundPool;importandroid.util.AttributeSet;importandroid.view.MotionEvent;importandroid.view.Surface;importandroid.view.SurfaceHolder;importandroid.view.SurfaceView;publicclassCannonViewextends SurfaceView implements SurfaceHolder.Callback{private CannonThread cannonThread;// controls the game loopprivate Activity activity;// to display Game Over dialog in GUI threadprivateboolean dialogIsDisplayed =false;// constants for game playpublicstaticfinalint TARGET_PIECES =7;// sections in the targetpublicstaticfinalint MISS_PENALTY =2;// seconds deducted on a misspublicstaticfinalint HIT_REWARD =3;// seconds added on a hit// variables for the game loop and tracking statisticsprivateboolean gameOver;// is the game over?privatedouble timeLeft;// the amount of time left in secondsprivateint shotsFired;// the number of shots the user has firedprivatedouble totalElapsedTime;// the number of seconds elapsed// variables for the blocker and targetprivate CannonModel blocker;// start and end points of the blockerprivateint blockerDistance;// blocker distance from leftprivateint blockerBeginning;// blocker distance from topprivateint blockerEnd;// blocker bottom edge distance from topprivateint initialBlockerVelocity;// initial blocker speed multiplierprivatefloat blockerVelocity;// blocker speed multiplier during gameprivate CannonModel target;// start and end points of the targetprivateint targetDistance;// target distance from leftprivateint targetBeginning;// target distance from topprivatedouble pieceLength;// length of a target pieceprivateint targetEnd;// target bottom's distance from topprivateint initialTargetVelocity;// initial target speed multiplierprivatefloat targetVelocity;// target speed multiplier during gameprivateint lineWidth;// width of the target and blockerprivateboolean[] hitStates;// is each target piece hit?privateint targetPiecesHit;// number of target pieces hit (out of 7)// variables for the cannon and cannonballprivate Point cannonball;// cannonball image's upper-left cornerprivateint cannonballVelocityX;// cannonball's x velocityprivateint cannonballVelocityY;// cannonball's y velocityprivateboolean cannonballOnScreen;// is the cannonball on the screenprivateint cannonballRadius;// cannonball radiusprivateint cannonballSpeed;// cannonball speedprivateint cannonBaseRadius;// cannon base radiusprivateint cannonLength;// cannon barrel lengthprivate Point barrelEnd;// the endpoint of the cannon's barrelprivateint screenWidth;// width of the screenprivateint screenHeight;// height of the screen// constants and variables for managing soundsprivatestaticfinalint TARGET_SOUND_ID =0;privatestaticfinalint CANNON_SOUND_ID =1;privatestaticfinalint BLOCKER_SOUND_ID =2;private SoundPool soundPool;// plays sound effectsprivate Map<Integer, Integer> soundMap;// maps IDs to SoundPool// Paint variables used when drawing each item on the screenprivate Paint textPaint;// Paint used to draw textprivate Paint cannonballPaint;// Paint used to draw the cannonballprivate Paint cannonPaint;// Paint used to draw the cannonprivate Paint blockerPaint;// Paint used to draw the blockerprivate Paint targetPaint;// Paint used to draw the targetprivate Paint backgroundPaint;// Paint used to clear the drawing area// public constructorpublicCannonView(Context context, AttributeSet attrs){super(context, attrs);// call super's constructor
activity =(Activity) context;// register SurfaceHolder.Callback listener
getHolder().addCallback(this);// initialize Lines and points representing game items
blocker =new CannonModel();// create the blocker as a Line
target =new CannonModel();// create the target as a Line
cannonball =new Point();// create the cannonball as a point// initialize hitStates as a boolean array
hitStates =newboolean[TARGET_PIECES];// initialize SoundPool to play the app's three sound effects
soundPool =new SoundPool(1, AudioManager.STREAM_MUSIC,0);// create Map of sounds and pre-load sounds
soundMap =new HashMap<Integer, Integer>();// create new HashMap
soundMap.put(TARGET_SOUND_ID,
soundPool.load(context, R.raw.target_hit,1));
soundMap.put(CANNON_SOUND_ID,
soundPool.load(context, R.raw.cannon_fire,1));
soundMap.put(BLOCKER_SOUND_ID,
soundPool.load(context, R.raw.blocker_hit,1));// construct Paints for drawing text, cannonball, cannon,// blocker and target; these are configured in method onSizeChanged
textPaint =new Paint();// Paint for drawing text
cannonPaint =new Paint();// Paint for drawing the cannon
cannonballPaint =new Paint();// Paint for drawing a cannonball
blockerPaint =new Paint();// Paint for drawing the blocker
targetPaint =new Paint();// Paint for drawing the target
backgroundPaint =new Paint();// Paint for drawing the target}// end CannonView constructor// called by surfaceChanged when the size of the SurfaceView changes,// such as when it's first added to the View hierarchy@OverrideprotectedvoidonSizeChanged(int w,int h,int oldw,int oldh){super.onSizeChanged(w, h, oldw, oldh);
screenWidth = w;// store the width
screenHeight = h;// store the height
cannonBaseRadius = h /18;// cannon base radius 1/18 screen height
cannonLength = w /8;// cannon length 1/8 screen width
cannonballRadius = w /36;// cannonball radius 1/36 screen width
cannonballSpeed = w *3/2;// cannonball speed multiplier
lineWidth = w /24;// target and blocker 1/24 screen width// configure instance variables related to the blocker
blockerDistance = w *5/8;// blocker 5/8 screen width from left
blockerBeginning = h /8;// distance from top 1/8 screen height
blockerEnd = h *3/8;// distance from top 3/8 screen height
initialBlockerVelocity = h /2;// initial blocker speed multiplier
blocker.start=new Point(blockerDistance, blockerBeginning);
blocker.end=new Point(blockerDistance, blockerEnd);// configure instance variables related to the target
targetDistance = w *7/8;// target 7/8 screen width from left
targetBeginning = h /8;// distance from top 1/8 screen height
targetEnd = h *7/8;// distance from top 7/8 screen height
pieceLength =(targetEnd - targetBeginning)/ TARGET_PIECES;
initialTargetVelocity =-h /4;// initial target speed multiplier
target.start=new Point(targetDistance, targetBeginning);
target.end=new Point(targetDistance, targetEnd);// endpoint of the cannon's barrel initially points horizontally
barrelEnd =new Point(cannonLength, h /2);// configure Paint objects for drawing game elements
textPaint.setTextSize(w /20);// text size 1/20 of screen width
textPaint.setAntiAlias(true);// smoothes the text
cannonPaint.setStrokeWidth(lineWidth *1.5f);// set line thickness
blockerPaint.setStrokeWidth(lineWidth);// set line thickness
targetPaint.setStrokeWidth(lineWidth);// set line thickness
backgroundPaint.setColor(Color.WHITE);// set background color
newGame();// set up and start a new game}// end method onSizeChanged// reset all the screen elements and start a new gamepublicvoidnewGame(){// set every element of hitStates to false--restores target piecesfor(int i =0; i < TARGET_PIECES;++i)
hitStates[i]=false;
targetPiecesHit =0;// no target pieces have been hit
blockerVelocity = initialBlockerVelocity;// set initial velocity
targetVelocity = initialTargetVelocity;// set initial velocity
timeLeft =10;// start the countdown at 10 seconds
cannonballOnScreen =false;// the cannonball is not on the screen
shotsFired =0;// set the initial number of shots fired
totalElapsedTime =0.0;// set the time elapsed to zero
blocker.start.set(blockerDistance, blockerBeginning);
blocker.end.set(blockerDistance, blockerEnd);
target.start.set(targetDistance, targetBeginning);
target.end.set(targetDistance, targetEnd);if(gameOver){
gameOver =false;// the game is not over
cannonThread =new CannonThread(getHolder());
cannonThread.start();}// end if}// end method newGame// called repeatedly by the CannonThread to update game elementsprivatevoidupdatePositions(double elapsedTimeMS){double interval = elapsedTimeMS /1000.0;// convert to secondsif(cannonballOnScreen)// if there is currently a shot fired{// update cannonball position
cannonball.x+= interval * cannonballVelocityX;
cannonball.y+= interval * cannonballVelocityY;// check for collision with blockerif(cannonball.x+ cannonballRadius > blockerDistance &&
cannonball.x- cannonballRadius < blockerDistance &&
cannonball.y+ cannonballRadius > blocker.start.y&&
cannonball.y- cannonballRadius < blocker.end.y){
cannonballVelocityX *=-1;// reverse cannonball's direction
timeLeft -= MISS_PENALTY;// penalize the user// play blocker sound
soundPool.play(soundMap.get(BLOCKER_SOUND_ID),1,1,1,0,1f);}// end if// check for collisions with left and right wallselseif(cannonball.x+ cannonballRadius > screenWidth ||
cannonball.x- cannonballRadius <0)
cannonballOnScreen =false;// remove cannonball from screen// check for collisions with top and bottom wallselseif(cannonball.y+ cannonballRadius > screenHeight ||
cannonball.y- cannonballRadius <0)
cannonballOnScreen =false;// make the cannonball disappear// check for cannonball collision with targetelseif(cannonball.x+ cannonballRadius > targetDistance &&
cannonball.x- cannonballRadius < targetDistance &&
cannonball.y+ cannonballRadius > target.start.y&&
cannonball.y- cannonballRadius < target.end.y){// determine target section number (0 is the top)int section =(int)((cannonball.y- target.start.y)/ pieceLength);// check if the piece hasn't been hit yetif((section >=0&& section < TARGET_PIECES)&&!hitStates[section]){
hitStates[section]=true;// section was hit
cannonballOnScreen =false;// remove cannonball
timeLeft += HIT_REWARD;// add reward to remaining time// play target hit sound
soundPool.play(soundMap.get(TARGET_SOUND_ID),1,1,1,0,1f);// if all pieces have been hitif(++targetPiecesHit == TARGET_PIECES){
cannonThread.setRunning(false);showGameOverDialog(R.string.win);// show winning dialog
gameOver =true;// the game is over}// end if}// end if}// end else if}// end if// update the blocker's positiondouble blockerUpdate = interval * blockerVelocity;
blocker.start.y+= blockerUpdate;
blocker.end.y+= blockerUpdate;// update the target's positiondouble targetUpdate = interval * targetVelocity;
target.start.y+= targetUpdate;
target.end.y+= targetUpdate;// if the blocker hit the top or bottom, reverse directionif(blocker.start.y<0|| blocker.end.y> screenHeight)
blockerVelocity *=-1;// if the target hit the top or bottom, reverse directionif(target.start.y<0|| target.end.y> screenHeight)
targetVelocity *=-1;
timeLeft -= interval;// subtract from time left// if the timer reached zeroif(timeLeft <=0.0){
timeLeft =0.0;
gameOver =true;// the game is over
cannonThread.setRunning(false); showGameOverDialog(R.string.lose);// show the losing dialog}// end if}// end method updatePositions// fires a cannonballpublicvoidfireCannonball(MotionEvent event){if(cannonballOnScreen)// if a cannonball is already on the screenreturn;// do nothingdouble angle = alignCannon(event);// get the cannon barrel's angle// move the cannonball to be inside the cannon
cannonball.x= cannonballRadius;// align x-coordinate with cannon
cannonball.y= screenHeight /2;// centers ball vertically// get the x component of the total velocity
cannonballVelocityX =(int)(cannonballSpeed * Math.sin(angle));// get the y component of the total velocity
cannonballVelocityY =(int)(-cannonballSpeed * Math.cos(angle));
cannonballOnScreen =true;// the cannonball is on the screen++shotsFired;// increment shotsFired// play cannon fired sound
soundPool.play(soundMap.get(CANNON_SOUND_ID),1,1,1,0,1f);}// end method fireCannonball// aligns the cannon in response to a user touchpublicdoublealignCannon(MotionEvent event){// get the location of the touch in this view
Point touchPoint =new Point((int) event.getX(),(int) event.getY());// compute the touch's distance from center of the screen// on the y-axisdouble centerMinusY =(screenHeight /2- touchPoint.y);double angle =0;// initialize angle to 0// calculate the angle the barrel makes with the horizontalif(centerMinusY !=0)// prevent division by 0
angle = Math.atan((double) touchPoint.x/ centerMinusY);// if the touch is on the lower half of the screenif(touchPoint.y> screenHeight /2)
angle += Math.PI;// adjust the angle// calculate the endpoint of the cannon barrel
barrelEnd.x=(int)(cannonLength * Math.sin(angle));
barrelEnd.y=(int)(-cannonLength * Math.cos(angle)+ screenHeight /2);return angle;// return the computed angle}// end method alignCannon// draws the game to the given CanvaspublicvoiddrawGameElements(Canvas canvas){// clear the background
canvas.drawRect(0,0, canvas.getWidth(), canvas.getHeight(),
backgroundPaint);// display time remaining
canvas.drawText(getResources().getString(
R.string.time_remaining_format, timeLeft),30,50, textPaint);// if a cannonball is currently on the screen, draw itif(cannonballOnScreen)
canvas.drawCircle(cannonball.x, cannonball.y, cannonballRadius,
cannonballPaint);// draw the cannon barrel
canvas.drawLine(0, screenHeight /2, barrelEnd.x, barrelEnd.y,
cannonPaint);// draw the cannon base
canvas.drawCircle(0,(int) screenHeight /2,(int) cannonBaseRadius, cannonPaint);// draw the blocker
canvas.drawLine(blocker.start.x, blocker.start.y, blocker.end.x,
blocker.end.y, blockerPaint);
Point currentPoint =new Point();// start of current target section// initialize curPoint to the starting point of the target
currentPoint.x= target.start.x;
currentPoint.y= target.start.y;// draw the targetfor(int i =1; i <= TARGET_PIECES;++i){// if this target piece is not hit, draw itif(!hitStates[i -1]){// alternate coloring the pieces yellow and blueif(i %2==0)
targetPaint.setColor(Color.YELLOW);else
targetPaint.setColor(Color.BLUE);
canvas.drawLine(currentPoint.x, currentPoint.y, target.end.x,(int)(currentPoint.y+ pieceLength), targetPaint);}// end if// move curPoint to the start of the next piece
currentPoint.y+= pieceLength;}// end for}// end method drawGameElements// display an AlertDialog when the game endsprivatevoidshowGameOverDialog(int messageId){// create a dialog displaying the given Stringfinal AlertDialog.Builder dialogBuilder =new AlertDialog.Builder(getContext());
dialogBuilder.setTitle(getResources().getString(messageId));
dialogBuilder.setCancelable(false);// display number of shots fired and total time elapsed// add the additional targetPiecesHit variable dialogBuilder.setMessage(getResources().getString(
R.string.results_format, shotsFired, totalElapsedTime)); dialogBuilder.setPositiveButton( R.string.reset_game,new DialogInterface.OnClickListener(){// called when "Reset Game" Button is pressed@OverridepublicvoidonClick(DialogInterface dialog,int which){
dialogIsDisplayed =false;
newGame();// set up and start a new game}// end method onClick}// end anonymous inner class);// end call to setPositiveButton
activity.runOnUiThread(newRunnable(){publicvoidrun(){
dialogIsDisplayed =true;
dialogBuilder.show();// display the dialog}// end method run}// end Runnable);// end call to runOnUiThread}// end method showGameOverDialog// stops the gamepublicvoidstopGame(){if(cannonThread !=null)
cannonThread.setRunning(false);}// end method stopGame// releases resources; called by CannonGame's onDestroy methodpublicvoidreleaseResources(){
soundPool.release();// release all resources used by the SoundPool
soundPool =null;}// end method releaseResources// called when surface changes size@OverridepublicvoidsurfaceChanged(SurfaceHolder holder,int format,int width,int height){}// end method surfaceChanged// called when surface is first created@OverridepublicvoidsurfaceCreated(SurfaceHolder holder){if(!dialogIsDisplayed){
cannonThread =new CannonThread(holder);
cannonThread.setRunning(true);
cannonThread.start();// start the game loop thread}// end if}// end method surfaceCreated// called when the surface is destroyed@OverridepublicvoidsurfaceDestroyed(SurfaceHolder holder){// ensure that thread terminates properlyboolean retry =true;
cannonThread.setRunning(false);while(retry){try{
cannonThread.join();
retry =false;}// end trycatch(InterruptedException e){}// end catch}// end while}// end method surfaceDestroyed// Thread subclass to control the game loopprivateclassCannonThreadextends Thread
{private SurfaceHolder surfaceHolder;// for manipulating canvasprivateboolean threadIsRunning =true;// running by default// initializes the surface holderpublicCannonThread(SurfaceHolder holder){
surfaceHolder = holder;
setName("CannonThread");}// end constructor// changes running statepublicvoidsetRunning(boolean running){
threadIsRunning = running;}// end method setRunning// controls the game loop@Overridepublicvoidrun(){
Canvas canvas =null;// used for drawinglong previousFrameTime = System.currentTimeMillis();while(threadIsRunning){try{
canvas = surfaceHolder.lockCanvas(null);// lock the surfaceHolder for drawingsynchronized(surfaceHolder){long currentTime = System.currentTimeMillis();double elapsedTimeMS = currentTime - previousFrameTime;
totalElapsedTime += elapsedTimeMS /1000.00;
updatePositions(elapsedTimeMS);// update game state
drawGameElements(canvas);// draw
previousFrameTime = currentTime;// update previous time}// end synchronized block}// end tryfinally{if(canvas !=null)
surfaceHolder.unlockCanvasAndPost(canvas);}// end finally}// end while}// end method run}// end nested class CannonThread}// end class CannonView
7.1.In the CannonController
class, add the following statements and methods in the appropriate places.
papackagemdad.game;importandroid.app.Activity;importandroid.media.AudioManager;importandroid.os.Bundle;importandroid.view.GestureDetector;importandroid.view.GestureDetector.SimpleOnGestureListener;importandroid.view.MotionEvent;publicclassCannonControllerextends Activity {private GestureDetector gestureDetector;// listens for double tapsprivate CannonView cannonView;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cannon_controller);cannonView =(CannonView) findViewById(R.id.cannonView);// get the CannonView gestureDetector =new GestureDetector(this, gestureListener);setVolumeControlStream(AudioManager.STREAM_MUSIC);}SimpleOnGestureListener gestureListener =new SimpleOnGestureListener(){// called when the user double taps the screenpublicbooleanonDoubleTap(MotionEvent e){
cannonView.fireCannonball(e);returntrue;}//};// end gestureListenerpublicvoidonPause(){super.onPause();
cannonView.stopGame();}
publicbooleanonTouchEvent(MotionEvent event){int action = event.getAction();// the user user touched the screen or dragged along the screenif(action == MotionEvent.ACTION_DOWN||action == MotionEvent.ACTION_MOVE){
cannonView.alignCannon(event);}return gestureDetector.onTouchEvent(event);}}