Issues accessing a class I created, Android Snake game - java

So I working on adding sound to an Android Snake game. What I am required to do is to add sound in for 3 events...
When the snake moves
When the snake eats
When the snake collides w/ itself or the wall
In order to do this I am using the SoundPool class and to sort of clean things up I just made separate class could SoundPlayer that sets up everything and has 3 methods to address the events. However, I am am trying not to restructure the code we were given too much but the primary code regarding movement and events is not in onCreate(). Where all examples I have practiced with say to generate the instantiate and add call the sound. Instead it is in its own .java file that is called in onCreate() so I am at my wits end on how and where I can instantiate the SoundPlayer class.
Can anyone out there help with how I can achieve the requirements and understand where I am going wrong with trying to use this class and its methods?
Snake.java
package com.example.android.snake;
import android.app.Activity;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.widget.TextView;
/**
* Snake: a simple game that everyone can enjoy.
*
* This is an implementation of the classic Game "Snake", in which you control a
* serpent roaming around the garden looking for apples. Be careful, though,
* because when you catch one, not only will you become longer, but you'll move
* faster. Running into yourself or the walls will end the game.
*
*/
public class Snake extends Activity {
private SnakeView mSnakeView;
private static String ICICLE_KEY = "snake-view";
//U01A1: Initialize Sound class
SoundPlayer sound;
public SoundPlayer getSound() {
return sound;
}
/**
* Called when Activity is first created. Turns off the title bar, sets up
* the content views, and fires up the SnakeView.
*
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.snake_layout);
mSnakeView = (SnakeView) findViewById(R.id.snake);
mSnakeView.setTextView((TextView) findViewById(R.id.text));
//sound
sound = new SoundPlayer(this); //sound
if (savedInstanceState == null) {
// We were just launched -- set up a new game
mSnakeView.setMode(SnakeView.READY);
} else {
// We are being restored
Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
if (map != null) {
mSnakeView.restoreState(map);
} else {
mSnakeView.setMode(SnakeView.PAUSE);
}
}
}
#Override
protected void onPause() {
super.onPause();
// Pause the game along with the activity
mSnakeView.setMode(SnakeView.PAUSE);
}
#Override
public void onSaveInstanceState(Bundle outState) {
//Store the game state
outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
}
}
SnakeView.java
package com.example.android.snake;
import java.util.ArrayList;
import java.util.Random;
import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
/**
* SnakeView: implementation of a simple game of Snake
*
*
*/
public class SnakeView extends TileView {
private static final String TAG = "SnakeView";
//private SoundPlayer sound;
/**
* Current mode of application: READY to run, RUNNING, or you have already
* lost. static final ints are used instead of an enum for performance
* reasons.
*/
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;
/**
* Current direction the snake is headed.
*/
private int mDirection = NORTH;
private int mNextDirection = NORTH;
private static final int NORTH = 1;
private static final int SOUTH = 2;
private static final int EAST = 3;
private static final int WEST = 4;
/**
* Labels for the drawables that will be loaded into the TileView class
*/
private static final int RED_STAR = 1;
private static final int YELLOW_STAR = 2;
private static final int GREEN_STAR = 3;
/**
* mScore: used to track the number of apples captured mMoveDelay: number of
* milliseconds between snake movements. This will decrease as apples are
* captured.
*/
private long mScore = 0;
private long mMoveDelay = 600;
/**
* mLastMove: tracks the absolute time when the snake last moved, and is used
* to determine if a move should be made based on mMoveDelay.
*/
private long mLastMove;
/**
* mStatusText: text shows to the user in some run states
*/
private TextView mStatusText;
/**
* mSnakeTrail: a list of Coordinates that make up the snake's body
* mAppleList: the secret location of the juicy apples the snake craves.
*/
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();
/**
* Everyone needs a little randomness in their life
*/
private static final Random RNG = new Random();
/**
* Create a simple handler that we can use to cause animation to happen. We
* set ourselves as a target and we can use the sleep()
* function to cause an update/invalidate to occur at a later date.
*/
private RefreshHandler mRedrawHandler = new RefreshHandler();
class RefreshHandler extends Handler {
#Override
public void handleMessage(Message msg) {
SnakeView.this.update();
SnakeView.this.invalidate();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
};
/**
* Constructs a SnakeView based on inflation from XML
*
* #param context
* #param attrs
*/
public SnakeView(Context context, AttributeSet attrs) {
super(context, attrs);
initSnakeView();
}
public SnakeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initSnakeView();
}
private void initSnakeView() {
setFocusable(true);
Resources r = this.getContext().getResources();
resetTiles(4);
loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
}
private void initNewGame() {
mSnakeTrail.clear();
mAppleList.clear();
// For now we're just going to load up a short default eastbound snake
// that's just turned north
mSnakeTrail.add(new Coordinate(7, 7));
mSnakeTrail.add(new Coordinate(6, 7));
mSnakeTrail.add(new Coordinate(5, 7));
mSnakeTrail.add(new Coordinate(4, 7));
mSnakeTrail.add(new Coordinate(3, 7));
mSnakeTrail.add(new Coordinate(2, 7));
mNextDirection = NORTH;
// Two apples to start with
addRandomApple();
addRandomApple();
mMoveDelay = 600;
mScore = 0;
}
/**
* Given a ArrayList of coordinates, we need to flatten them into an array of
* ints before we can stuff them into a map for flattening and storage.
*
* #param cvec : a ArrayList of Coordinate objects
* #return : a simple array containing the x/y values of the coordinates
* as [x1,y1,x2,y2,x3,y3...]
*/
private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
int count = cvec.size();
int[] rawArray = new int[count * 2];
for (int index = 0; index < count; index++) {
Coordinate c = cvec.get(index);
rawArray[2 * index] = c.x;
rawArray[2 * index + 1] = c.y;
}
return rawArray;
}
/**
* Save game state so that the user does not lose anything
* if the game process is killed while we are in the
* background.
*
* #return a Bundle with this view's state
*/
public Bundle saveState() {
Bundle map = new Bundle();
map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
map.putInt("mDirection", Integer.valueOf(mDirection));
map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
map.putLong("mScore", Long.valueOf(mScore));
map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));
return map;
}
/**
* Given a flattened array of ordinate pairs, we reconstitute them into a
* ArrayList of Coordinate objects
*
* #param rawArray : [x1,y1,x2,y2,...]
* #return a ArrayList of Coordinates
*/
private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();
int coordCount = rawArray.length;
for (int index = 0; index < coordCount; index += 2) {
Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
coordArrayList.add(c);
}
return coordArrayList;
}
/**
* Restore game state if our process is being relaunched
*
* #param icicle a Bundle containing the game state
*/
public void restoreState(Bundle icicle) {
setMode(PAUSE);
mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
mDirection = icicle.getInt("mDirection");
mNextDirection = icicle.getInt("mNextDirection");
mMoveDelay = icicle.getLong("mMoveDelay");
mScore = icicle.getLong("mScore");
mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
}
/*
* handles key events in the game. Update the direction our snake is traveling
* based on the DPAD. Ignore events that would cause the snake to immediately
* turn back on itself.
*
* (non-Javadoc)
*
* #see android.view.View#onKeyDown(int, android.os.KeyEvent)
*/
#Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if (mMode == READY | mMode == LOSE) {
/*
* At the beginning of the game, or the end of a previous one,
* we should start a new game.
*/
initNewGame();
setMode(RUNNING);
update();
return (true);
}
if (mMode == PAUSE) {
/*
* If the game is merely paused, we should just continue where
* we left off.
*/
setMode(RUNNING);
update();
return (true);
}
if (mDirection != SOUTH) {
mNextDirection = NORTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if (mDirection != NORTH) {
mNextDirection = SOUTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
if (mDirection != EAST) {
mNextDirection = WEST;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (mDirection != WEST) {
mNextDirection = EAST;
}
return (true);
}
return super.onKeyDown(keyCode, msg);
}
/**
* Sets the TextView that will be used to give information (such as "Game
* Over" to the user.
*
* #param newView
*/
public void setTextView(TextView newView) {
mStatusText = newView;
}
/**
* Updates the current mode of the application (RUNNING or PAUSED or the like)
* as well as sets the visibility of textview for notification
*
* #param newMode
*/
public void setMode(int newMode) {
int oldMode = mMode;
mMode = newMode;
if (newMode == RUNNING & oldMode != RUNNING) {
mStatusText.setVisibility(View.INVISIBLE);
update();
return;
}
Resources res = getContext().getResources();
CharSequence str = "";
if (newMode == PAUSE) {
str = res.getText(R.string.mode_pause);
}
if (newMode == READY) {
str = res.getText(R.string.mode_ready);
}
if (newMode == LOSE) {
str = res.getString(R.string.mode_lose_prefix) + mScore
+ res.getString(R.string.mode_lose_suffix);
}
mStatusText.setText(str);
mStatusText.setVisibility(View.VISIBLE);
}
/**
* Selects a random location within the garden that is not currently covered
* by the snake. Currently _could_ go into an infinite loop if the snake
* currently fills the garden, but we'll leave discovery of this prize to a
* truly excellent snake-player.
*
*/
private void addRandomApple() {
Coordinate newCoord = null;
boolean found = false;
while (!found) {
// Choose a new location for our apple
int newX = 1 + RNG.nextInt(mXTileCount - 2);
int newY = 1 + RNG.nextInt(mYTileCount - 2);
newCoord = new Coordinate(newX, newY);
// Make sure it's not already under the snake
boolean collision = false;
int snakelength = mSnakeTrail.size();
for (int index = 0; index < snakelength; index++) {
if (mSnakeTrail.get(index).equals(newCoord)) {
collision = true;
}
}
// if we're here and there's been no collision, then we have
// a good location for an apple. Otherwise, we'll circle back
// and try again
found = !collision;
}
if (newCoord == null) {
Log.e(TAG, "Somehow ended up with a null newCoord!");
}
mAppleList.add(newCoord);
}
/**
* Handles the basic update loop, checking to see if we are in the running
* state, determining if a move should be made, updating the snake's location.
*/
public void update() {
if (mMode == RUNNING) {
long now = System.currentTimeMillis();
if (now - mLastMove > mMoveDelay) {
clearTiles();
updateWalls();
updateSnake();
updateApples();
mLastMove = now;
}
mRedrawHandler.sleep(mMoveDelay);
}
}
/**
* Draws some walls.
*
*/
private void updateWalls() {
for (int x = 0; x < mXTileCount; x++) {
setTile(GREEN_STAR, x, 0);
setTile(GREEN_STAR, x, mYTileCount - 1);
}
for (int y = 1; y < mYTileCount - 1; y++) {
setTile(GREEN_STAR, 0, y);
setTile(GREEN_STAR, mXTileCount - 1, y);
}
}
/**
* Draws some apples.
*
*/
private void updateApples() {
for (Coordinate c : mAppleList) {
setTile(YELLOW_STAR, c.x, c.y);
}
}
/**
* Figure out which way the snake is going, see if he's run into anything (the
* walls, himself, or an apple). If he's not going to die, we then add to the
* front and subtract from the rear in order to simulate motion. If we want to
* grow him, we don't subtract from the rear.
*
*/
private void updateSnake() {
boolean growSnake = false;
// grab the snake by the head
Coordinate head = mSnakeTrail.get(0);
Coordinate newHead = new Coordinate(1, 1);
mDirection = mNextDirection;
switch (mDirection) {
case EAST: {
newHead = new Coordinate(head.x + 1, head.y);
break;
}
case WEST: {
newHead = new Coordinate(head.x - 1, head.y);
break;
}
case NORTH: {
newHead = new Coordinate(head.x, head.y - 1);
break;
}
case SOUTH: {
newHead = new Coordinate(head.x, head.y + 1);
break;
}
}
// Collision detection
// For now we have a 1-square wall around the entire arena
if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
|| (newHead.y > mYTileCount - 2)) {
setMode(LOSE);
return;
}
// Look for collisions with itself
int snakelength = mSnakeTrail.size();
for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
Coordinate c = mSnakeTrail.get(snakeindex);
if (c.equals(newHead)) {
setMode(LOSE);
return;
}
}
// Look for apples
int applecount = mAppleList.size();
for (int appleindex = 0; appleindex < applecount; appleindex++) {
Coordinate c = mAppleList.get(appleindex);
if (c.equals(newHead)) {
mAppleList.remove(c);
addRandomApple();
mScore++;
mMoveDelay *= 0.9;
growSnake = true;
}
}
// push a new head onto the ArrayList and pull off the tail
mSnakeTrail.add(0, newHead);
// except if we want the snake to grow
if (!growSnake) {
mSnakeTrail.remove(mSnakeTrail.size() - 1);
}
int index = 0;
for (Coordinate c : mSnakeTrail) {
if (index == 0) {
setTile(YELLOW_STAR, c.x, c.y);
} else {
setTile(RED_STAR, c.x, c.y);
}
index++;
}
}
/**
* Simple class containing two integer values and a comparison function.
* There's probably something I should use instead, but this was quick and
* easy to build.
*
*/
private class Coordinate {
public int x;
public int y;
public Coordinate(int newX, int newY) {
x = newX;
y = newY;
}
public boolean equals(Coordinate other) {
if (x == other.x && y == other.y) {
return true;
}
return false;
}
#Override
public String toString() {
return "Coordinate: [" + x + "," + y + "]";
}
}
}
SoundPlayer.java
package com.example.android.snake;
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
public class SoundPlayer {
// U01A1: variables for SoundPool
private SoundPool soundPool; //var for soundPool
private int sound1, sound2, sound3; //var for sound files, are int because ids are ints
public SoundPlayer(Context context) {
//SoundPool (int maxStreams, int streamType, int srcQuality)
soundPool = new SoundPool (3, AudioManager.STREAM_MUSIC, 0);
//loading the sounds; priority set to 1
sound1 = soundPool.load(context, R.raw.sound1, 1);
sound2 = soundPool.load(context, R.raw.sound2, 1);
sound3 = soundPool.load(context, R.raw.sound3, 1);
}
//method to play sound1
public void playMoveSound() {
// play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
soundPool.play(sound1, 1,1,0,0,1);
}
//method to play sound2
public void playEatSound() {
soundPool.play(sound2, 1,1,0,0,1);
}
//method to play sound3
public void playHitsound() {
soundPool.play(sound3, 1,1,0,0,1);
}
}

Related

Why is my function not doing what its supposed to, changing the direction of the velocity of a ball in javafx?

So I have made a little program, where i have a ball that is supposed to slowly accelerate when falling down and slowly decelerate when jumping up again. Meanwhile it is supposed to move to the left and to the right, basically bouncing from side to side.
The problem I have now, is that the method that is supposed to make the ball change velocity on the y axis when the ball hits the bottom of the canvas, stops changing the algebraic sign, after the first successful jump/bounce and the ball glitches at the bottom.
I have tried printing out some stuff if it gets into the function moveBubbleUp() and it printed when it was glitching at the bottom of the screen and was supposed to move up again, so why wont the algebraic sign change? When printing the velocity, it gets bigger and bigger, even though the moveBubbleUp() has bubbleVelocity-=5 in it.
So the method gets called, proven by printing a random string when it enters it, but doesn't do anything i want it to, while also doing exactly what its counterpart moveBubbleDown() is supposed to do.
Which doesn't get called when the glitch happens, since i have tried printing something there as well.
Here are the methods that change the velocity:
void moveBubbleDown() {
bubbleSpeedY += 5;
bubbleVelocityY = bubbleSpeedY;
}
void moveBubbleUp() {
bubbleSpeedY -= 5;
bubbleVelocityY = -bubbleSpeedY;
}
and here are the methods, that call the functions and then change the y-Pos depending on them :
//calculates YPos of bubble
//changes which method should be called, if the ball has left the border
//and sets him on the edge of it
if (bubble.getTranslateY()>=heightPane-radiusBubble) {
bubble.setTranslateY(heightPane-radiusBubble);
moveDown=false;
} else if (bubble.getTranslateY() <= maxHeight) {
bubble.setTranslateY(maxHeight);
moveDown=true;
}
if (moveDown) {
moveBubbleDown();
} else {
moveBubbleUp();
}
final double deltaYBubble = elapsedSeconds * bubbleVelocityY;
bubble.setTranslateY(bubble.getTranslateY() + deltaYBubble);
To not leave anything out i'll post the rest of the code, so that you can try it yourself aswell:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
//Global Variables
private static final int widthPane = 1000;
private static final int heightPane = 650;
long lastUpdateTime = 0;
//canvas
private Pane root = new Pane();
//Enemy
int bubbleSpeedY = 30;
int bubbleVelocityY = bubbleSpeedY;
final int bubbleSpeedX = 200;
int bubbleVelocityX = 0;
int radiusBubble = 25;
boolean moveDown=true;
boolean moveRight=true;
int maxHeight = 50;
private Circle bubble = new Circle(radiusBubble, Color.CADETBLUE);
private Parent createContent() {
root.setPrefSize(widthPane, heightPane);
root.getChildren().addAll(bubble);
return root;
}
#Override
public void start(Stage stage) throws Exception{
bubble.setTranslateY(maxHeight);
bubble.setTranslateX((int)(Math.random()*widthPane));
Scene scene = new Scene(createContent());
final AnimationTimer gameAnimation = new AnimationTimer() {
#Override
public void handle(long timestamp) {
update(timestamp);
}
};
gameAnimation.start();
stage.setScene(scene);
stage.show();
}
/**
* changes velocity so that the bubble goes to the right
*/
void moveBubbleRight() {
bubbleVelocityX = bubbleSpeedX;
}
/**
* changes velocity so that the bubble goes to the left
*/
void moveBubbleLeft() {
bubbleVelocityX =-bubbleSpeedX;
}
void moveBubbleDown() {
bubbleSpeedY += 5;
bubbleVelocityY = bubbleSpeedY;
}
void moveBubbleUp() {
bubbleSpeedY -= 5;
bubbleVelocityY = -bubbleSpeedY;
}
/**
* updates the pos of the bubble depending on the velocity,
* looks if it's about to leave the border,
* and sets it on the edge if it is
*
* #param timestamp helps calculate the time that has passed since the last update / frame
*/
private void update(long timestamp) {
if (lastUpdateTime > 0) {
final double elapsedSeconds = (timestamp - lastUpdateTime) / 1_000_000_000.0 ;
//bubble
//calculates YPos of bubble
//changes which method should be called, if the ball has left the border
//and sets him on the edge of it
if (bubble.getTranslateY()>=heightPane-radiusBubble) {
bubble.setTranslateY(heightPane-radiusBubble);
moveDown=false;
} else if (bubble.getTranslateY() <= maxHeight) {
bubble.setTranslateY(maxHeight);
moveDown=true;
}
if (moveDown) {
moveBubbleDown();
} else {
moveBubbleUp();
}
final double deltaYBubble = elapsedSeconds * bubbleVelocityY;
bubble.setTranslateY(bubble.getTranslateY() + deltaYBubble);
//calculates XPos of Bubble
if (bubble.getTranslateX() >= widthPane - radiusBubble) {
bubble.setTranslateX(widthPane - radiusBubble);
moveRight=false;
} else if (bubble.getTranslateX() - radiusBubble <= 0) {
moveRight=true;
}
if (moveRight) {
moveBubbleRight();
} else {
moveBubbleLeft();
}
final double deltaXBubble = elapsedSeconds * bubbleVelocityX;
bubble.setTranslateX(bubble.getTranslateX() + deltaXBubble);
}
lastUpdateTime=timestamp;
}
public static void main(String[] args) {
launch(args);
}
}
Thanks for everyone trying to help, since I'm not so familiar with coding yet, and I think the problem is far beyond my knowledge of code. Well, or maybe its a stupidly small error that I have overlooked thats ruining everything.
It has to do with the velocity at the time you hit the "floor". Simple timing changes can make it so it doesn't happen. I think the velocity is such that the bubble doesn't properly get away from the floor so it is stuck triggering the condition of hitting the floor. I added a print of vertical bubble velocity right after the moveUp/Down calls and then it stopped getting stuck!
I've made a minor change to your code to simplify the vertical movement and things seem to be better:
package stackoverflow.answers.demo;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
//Global Variables
private static final int widthPane = 1000;
private static final int heightPane = 650;
long lastUpdateTime = 0;
//canvas
private Pane root = new Pane();
//Enemy
double bubbleAccelerationY = 300;
double bubbleSpeedY = 30;
double bubbleVelocityY = bubbleSpeedY;
final int bubbleSpeedX = 200;
int bubbleVelocityX = 0;
int radiusBubble = 25;
boolean moveDown = true;
boolean moveRight = true;
int maxHeight = 50;
private Circle bubble = new Circle(radiusBubble, Color.CADETBLUE);
private Parent createContent() {
root.setPrefSize(widthPane, heightPane);
root.getChildren().addAll(bubble);
return root;
}
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
bubble.setTranslateY(maxHeight);
bubble.setTranslateX((int) (Math.random() * widthPane));
Scene scene = new Scene(createContent());
final AnimationTimer gameAnimation = new AnimationTimer() {
#Override
public void handle(long timestamp) {
update(timestamp);
}
};
gameAnimation.start();
stage.setScene(scene);
stage.show();
}
/**
* changes velocity so that the bubble goes to the right
*/
void moveBubbleRight() {
bubbleVelocityX = bubbleSpeedX;
}
/**
* changes velocity so that the bubble goes to the left
*/
void moveBubbleLeft() {
bubbleVelocityX = -bubbleSpeedX;
}
void accelerateBubble(double secs) {
bubbleVelocityY += (secs * bubbleAccelerationY);
}
void bounceBubble() {
bubbleVelocityY = -bubbleVelocityY;
}
/**
* updates the pos of the bubble depending on the velocity, looks if it's about to leave the border, and sets it on
* the edge if it is
*
* #param timestamp helps calculate the time that has passed since the last update / frame
*/
private void update(long timestamp) {
if (lastUpdateTime > 0) {
final double elapsedSeconds = (timestamp - lastUpdateTime) / 1_000_000_000.0;
//bubble
//calculates YPos of bubble
//changes which method should be called, if the ball has left the border
//and sets him on the edge of it
if (bubble.getTranslateY() >= heightPane - radiusBubble) {
bubble.setTranslateY(heightPane - radiusBubble);
bounceBubble();
} else if (bubble.getTranslateY() <= maxHeight) {
bubble.setTranslateY(maxHeight);
}
accelerateBubble(elapsedSeconds);
final double deltaYBubble = elapsedSeconds * bubbleVelocityY;
bubble.setTranslateY(bubble.getTranslateY() + deltaYBubble);
//calculates XPos of Bubble
if (bubble.getTranslateX() >= widthPane - radiusBubble) {
bubble.setTranslateX(widthPane - radiusBubble);
moveRight = false;
} else if (bubble.getTranslateX() - radiusBubble <= 0) {
moveRight = true;
}
if (moveRight) {
moveBubbleRight();
} else {
moveBubbleLeft();
}
final double deltaXBubble = elapsedSeconds * bubbleVelocityX;
bubble.setTranslateX(bubble.getTranslateX() + deltaXBubble);
}
lastUpdateTime = timestamp;
}
}

.getSurface().isValid() without Callback

I am trying to use one MainActivity.java class in Android Studio to control an RC car, I am still in the works as this is an early prototype code.
I am currently struggling on why my surfaceHolder.getSurface().isValid() is returning false every time. I cannot find any help besides implementing a Callback class, however, previously I have been able to use precisely the same lines of code mimicking my surfaceHolder and never needed a Callback class, the only exception was the class extended SurfaceView which this one does not because I can only extend one class. Below is my code:
package com.example.hughman.cccontrol;
import android.graphics.Point;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.widget.ImageButton;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
public class MainActivity extends AppCompatActivity implements View.OnClickListener, Runnable{
private ImageButton breakButton, connectButton;
private Bitmap left_thumb, left_thumb_resized, right_thumb_resized;
private Context context;
private int screenW, screenH, thumb_x, thumb_y, drag_x, drag_y, current_thumb = -1;
private Canvas canvas;
private Paint paint = new Paint();
private SurfaceHolder surfaceHolder;
private Thread thread;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
SurfaceView surfaceView = new SurfaceView(this);
paint.setAntiAlias(true);
//puts the app in full screen mode
requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
surfaceHolder = surfaceView.getHolder();
//initalize screen size
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
screenH = size.x;
screenW = size.x;
//initialize buttons
breakButton = findViewById(R.id.breakbutton);
connectButton = findViewById(R.id.connectbutton);
breakButton.setOnClickListener(this);
connectButton.setOnClickListener(this);
left_thumb = BitmapFactory.decodeResource(this.getResources(), R.drawable.thumbstick);
left_thumb_resized = Bitmap.createScaledBitmap(left_thumb, 500, 500, false);
right_thumb_resized = Bitmap.createScaledBitmap(left_thumb, 500, 500, false);
thread = new Thread(this);
thread.start();
}
#Override
public void run() {
if(current_thumb == -1) {
drawInitialBitmap();
}
else
{
reDraw(drag_x, drag_y);
}
System.out.println("Run Test");
setFPS();
}
public void drawInitialBitmap()
{
thumb_x = 800;
thumb_y = (screenH / 2) + 250;
if (surfaceHolder.getSurface().isValid()) {
System.out.println("Hello");
canvas = surfaceHolder.lockCanvas();
canvas.drawBitmap(left_thumb_resized, thumb_x, thumb_y, paint);
canvas.drawBitmap(right_thumb_resized, thumb_x * 2, thumb_y, paint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
public void reDraw(int x, int y)
{
if (surfaceHolder.getSurface().isValid()) {
canvas = surfaceHolder.lockCanvas();
if (current_thumb == 1) {
canvas.drawBitmap(left_thumb_resized, x, y, paint);
} else if (current_thumb == 2) {
canvas.drawBitmap(right_thumb_resized, x, y, paint);
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
//method for when user selects an xml button
public void onClick(View v) {
if (v == connectButton) {
//prompts to connect to Bluetooth
System.out.println("ConectButton");
}
else if(v == breakButton)
{
System.out.println("BreakButton");
//halt all motors
}
}
public boolean onTouchEvent(MotionEvent event){
int action = event.getAction();
int drag_x = (int) event.getX(); // or getRawX();
int drag_y = (int) event.getY();
switch(action){
case MotionEvent.ACTION_DOWN:
if (drag_x >= thumb_x && drag_x < (thumb_x + left_thumb_resized.getWidth())
&& drag_y >= thumb_y && drag_y < (thumb_y + left_thumb_resized.getHeight())) {
current_thumb = 1;
return true;
}
else if (drag_x >= thumb_x * 2 && drag_x < ((thumb_x * 2) + right_thumb_resized.getWidth())
&& drag_y >= thumb_y && drag_y < (thumb_y + right_thumb_resized.getHeight())) {
current_thumb = 2;
return true;
}
break;
case MotionEvent.ACTION_MOVE:
if(current_thumb != -1)
{
System.out.println(Math.sqrt(Math.pow(drag_x - thumb_x, 2) + Math.pow(drag_y - thumb_y, 2)));
System.out.println(Math.sqrt(Math.pow(drag_x - (thumb_x * 2), 2) + Math.pow(drag_y - thumb_y, 2)));
//move left thumb
return true;
}
break;
case MotionEvent.ACTION_UP:
current_thumb = -1;
drawInitialBitmap();
break;
default:
}
return false;
}
public void setFPS() {
try {
thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Everything runs fine and builds properly, but neither thumb sticks are being drawn. I haven't restricted the movement of the thumb sticks, but later on I will add that. As for now, I want to be able to drag them in the direction I want to move, using the distance function to simply figure out how far I ideally want to be able to drag them.
Is it possible to do this without extending SufaceView? Or making a Callback class? Why? Below I will add the code from my previous project where I did not use a Callback class, keep in mind the code is messy as we had multiple people working on the project and it had a required deadline of 24 hours.
package com.example.michael.doyouknowdeway;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageButton;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.MediaPlayer;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Created by michael on 1/27/18.
* The big class that does most of the work for the code, sets the overall view of the game, getting
* the character, tiles, and player motions all together for a working game. Took us the most time
* to work on with many bugs occuring along de wae.
*/
public class GameView extends SurfaceView implements Runnable{
//
volatile boolean isPlaying = true, init = true, isPassOver = true;
private Thread gameThread = null;
private SurfaceHolder surfaceHolder;
private Canvas canvas;
private Context context;
private Activity activity;
private int screenWidth = 0, screenHeight = 0, move_const = 1;
private Player player;
private MediaPlayer jumpNoise, eatNoise;
private Bitmap backgroundImage;
private MediaPlayer backgroundMusic;
private MediaPlayer endGameSound;
private Bitmap backgroundImageResized;
Tile currentTile, nextTile;
private ScheduledExecutorService executorService;
private Paint paint = new Paint();
private Paint textPaint = new Paint();
private FireBall fireball;
private int scoreCount = 0, passOver;
private Bitmap endImage;
private Bitmap endImageResized;
private Bitmap run1, podCount;
private Bitmap run1Resized, podCountResized;
private Bitmap run2;
private Bitmap playerJumpImage;
private boolean isRun1 = false;
private ImageButton redoButton;
//starts of the program, creating the background for the game based on the dimensions of the
//phone being used
public GameView(Context context, int screenX, int screenY) {
super(context);
//load images into game
backgroundImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.background_sky);
backgroundImageResized = Bitmap.createScaledBitmap(backgroundImage, screenX, screenY, false);
podCount = BitmapFactory.decodeResource(context.getResources(), R.drawable.detergent_pod);
run1 = BitmapFactory.decodeResource(context.getResources(), R.drawable.knuckles_run);
run1Resized = Bitmap.createScaledBitmap(run1, 200, 200, false);
podCountResized = Bitmap.createScaledBitmap(podCount, 100, 100, false);
run2 = BitmapFactory.decodeResource(context.getResources(), R.drawable.ugandan_knuckle);
playerJumpImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.knucklesjump);
//load sounds into game
jumpNoise = MediaPlayer.create(context, R.raw.jump_takeoff);
eatNoise = MediaPlayer.create(context, R.raw.eat_1);
backgroundMusic = MediaPlayer.create(context, R.raw.music_baby);
endGameSound = MediaPlayer.create(context, R.raw.end_game);
//initialize other important stuff
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(72);
textPaint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
screenWidth = screenX;
screenHeight = screenY;
activity = (Activity) context;
backgroundMusic.start();
this.context = context;
player = new Player(context, screenX, screenY);
fireball = new FireBall(context, screenX, screenY);
currentTile = new Tile(context, 3, screenWidth + 400, screenHeight);
currentTile.fillTile();
surfaceHolder = getHolder();
//controls "running" animation
executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
if(!player.isJumping) {
if (!isRun1) {
run1Resized = Bitmap.createScaledBitmap(run1, 200, 200, false);
isRun1 = true;
} else {
run1Resized = Bitmap.createScaledBitmap(run2, 200, 200, false);
isRun1 = false;
}
}
}
}, 0, 200, TimeUnit.MILLISECONDS); //can change "speed" of run by altering the second param
}
/**
* Main game loop
*/
public void run() {
while (isPlaying) {
update();
draw();
setFPS();
}
}
/**
* Redraws the screen in the new positions, creating continuous movement for the game
*/
public void draw() {
if (surfaceHolder.getSurface().isValid()) {
canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(backgroundImageResized, 0, 0, paint);
canvas.drawBitmap(podCountResized, 0, 0, paint);
String scoreCountStr = Integer.toString(scoreCount);
if(0 >= (currentTile.getLength() * 100) - move_const)
{
currentTile = new Tile(nextTile);
isPassOver = true;
nextTile = null;
move_const = 0;
}
if(init) {
init = false;
for (int i = 0; i < currentTile.getLength(); i++) {
for (int j = currentTile.getHeight() - 1; j >= 0; j--) {
if (currentTile.getBlock(i, j) != null) {
canvas.drawBitmap(currentTile.getBlock(i, j).getImage(), (i * 100), (j * 100) + 10, paint);
}
}
}
}
else
{
for (int i = 0; i < currentTile.getLength(); i++) {
for (int j = currentTile.getHeight() - 1; j >= 0; j--) {
if (currentTile.getBlock(i, j) != null) {
canvas.drawBitmap(currentTile.getBlock(i, j).getImage(), (i * 100) - move_const, (j * 100) + 10, paint);
}
if (nextTile != null) {
if (i < nextTile.getLength() && j < nextTile.getHeight()) {
if (nextTile.getBlock(i, j) != null) {
canvas.drawBitmap(nextTile.getBlock(i, j).getImage(), ((i + currentTile.getLength()) * 100) - move_const, (j * 100) + 10, paint);
}
}
}
}
}
move_const += 10;
}
if(fireball.isShooting) {
canvas.drawBitmap(fireball.getImage(), fireball.getXVal(), fireball.getYVal(), paint);
}
canvas.drawBitmap(run1Resized,player.getXVal(), player.getYVal(), paint);
canvas.drawText(scoreCountStr, 120, 80, textPaint);
//releases the canvas to be redrawn again
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
/**
* updates the positions of everything on the screen if needed
*/
public void update() {
player.update();
if(fireball.isShooting) {
fireball.update(player);
}
if((((currentTile.getLength() - (screenWidth/100) )* 100) - move_const <= 200) && nextTile == null){
nextTile = currentTile.getNextTile();
}
if(player.getYVal() >= screenHeight || player.getXVal() <= 0){
gameOver();
}
detectCollisions();
}
//initially sets player to not be colliding - changed almost instantly
static boolean isColliding = false;
//
/**
* Detects collisions so the player can collect tide pods and jump off of surfaces.
*/
public void detectCollisions(){
int highestY = 9;
int currentX = (250 + move_const)/100;
if(currentX >= currentTile.getLength() && isPassOver)
{
passOver = -10;
isPassOver = false;
}
else
{
passOver= -25; //arbitrary
}
for(int i = 0; i < currentTile.getHeight(); i++)
{
if(currentX >= currentTile.getLength())
{
passOver += 10;
if(nextTile.getBlock(passOver/100, i) != null)
{
if(!nextTile.getBlock(passOver/100, i).isPod())
{
highestY = i;
break;
}
}
}
else if(currentTile.getBlock(currentX, i) != null)
{
if(!currentTile.getBlock(currentX, i).isPod())
{
highestY = i;
break;
}
}
else
{
highestY = -1;
}
}
checkGroundCollision(highestY);
checkTidePodCollision(currentTile, nextTile);
checkForwardCollision(nextTile, currentX, highestY, passOver);
}
/**
* Method used to check if the player has hit a wall in front of them.
* #param next - the next tile
* #param x - player x position
* #param y - player y position
* #param passOver - the location being passed over.
*/
public void checkForwardCollision(Tile next, int x, int y, int passOver)
{
boolean collision = false;
Rect rect = new Rect();
if(next != null && passOver >= 0)
{
rect.top = y * 100;
rect.bottom = screenHeight;
rect.left = passOver + (player.getBitmap().getWidth() / 2);
rect.right = passOver + (player.getBitmap().getWidth() / 2) + 100;
collision = Rect.intersects(player.getHitBox(), rect);
}
else
{
rect.top = y * 100;
rect.bottom = screenHeight;
rect.left = (x+1)* 100;
rect.right = (x+2) * 100;
collision = Rect.intersects(player.getHitBox(), rect);
}
//if collision is true, half player movement until its not true
}
/**
* Method that checks if a player has hit a tidepod, if so, adds to score count
* #param current - the current tile
* #param next - the next tile
*/
public void checkTidePodCollision(Tile current, Tile next)
{
if(next != null && !isPassOver)
{
for(double iter: next.getTidePods())
{
int x = (int) iter;
int y = (int) (iter - x)*10;
boolean hit = podCollision(x, y);
if(hit)
{
eatNoise.start();
scoreCount += 10;
System.out.println("Cur Next: " + x + " || " + y);
nextTile.setNullBlock(x, y);
return;
}
}
}
else
{
for(double iter: current.getTidePods())
{
int x = (int) iter;
double temp = x;
int y = (int) ((iter - temp)*10.00);
boolean hit = podCollision(x, y);
if(hit)
{
eatNoise.start();
scoreCount += 10;
System.out.println("Current: " + x + " || " + y);
currentTile.setNullBlock(x, y);
return;
}
}
}
}
//compliment to the previous method
private boolean podCollision(int x, int y) {
Rect tideRect = new Rect();
if(isPassOver && x < 3)
{
System.out.println("TEST: " + x + " || " + y);
tideRect.top = (y * 100);
tideRect.left = x * 100 + passOver + 10;
tideRect.right = (x + 2) * 100 + passOver;
tideRect.bottom = (y + 2) * 100 + 10;
}
else {
tideRect.top = y * 100;
tideRect.left = x * 100 - move_const + 10;
tideRect.right = (x + 2) * 100 - move_const;
tideRect.bottom = (y + 2) * 100 + 10;
}
return Rect.intersects(player.getHitBox(), tideRect);
}
/**
* Method used for jumping off of the ground.
* #param highestY
*/
private void checkGroundCollision(int highestY) {
Rect blockRect = new Rect();
boolean GroundCollision;
if(highestY >= 0) {
blockRect.top = (highestY) * 100 - 25;
blockRect.left = 200;
blockRect.right = 300;
//changed this valued -- this is to remind myself
blockRect.bottom = highestY * 100 + 25; //still needs work //make player hitbox just his feet
GroundCollision = Rect.intersects(player.getFeetBox(), blockRect);
System.out.println("WWWWWW : " + GroundCollision);
}
else
{
GroundCollision = false;
}
if(GroundCollision){
isColliding = true;
} else {
player.isFalling = true;
isColliding = false;
}
}
/**
* Sets the FPS to roughly 60 fps
*/
public void setFPS() {
try {
gameThread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Method that
*/
public void gameOver() {
//end image is not currently working
endImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.end_game);
endImageResized = Bitmap.createScaledBitmap(endImage, 100, 200, false);
canvas.drawBitmap(endImageResized, screenWidth/2, screenHeight/2, paint);
//free up memory from bitmaps
backgroundImage.recycle();
backgroundImage = null;
backgroundImageResized.recycle();
backgroundImageResized = null;
podCount.recycle();
podCount = null;
podCountResized.recycle();
podCountResized = null;
backgroundImage.recycle();
backgroundImage = null;
run1.recycle();
run1 = null;
run1Resized.recycle();
run1Resized = null;
run2.recycle();
run2 = null;
playerJumpImage.recycle();
playerJumpImage = null;
backgroundMusic.stop();
Runtime.getRuntime().gc(); //manually run garbage collector
endGameSound.start();
context.startActivity(new Intent(context,MainActivity.class));
}
/**
* Method used to dictate what to do when the android screen is touched.
* #param event - the type of touch on the screen
* #return - true when screen is touched
*/
public boolean onTouchEvent(MotionEvent event){
int touchAction = event.getAction();
if(touchAction == MotionEvent.ACTION_DOWN){
if(event.getX() < (screenWidth / 2)) {
jumpNoise.start();
if(!player.isJumping && !player.isFalling) {
player.setYval(player.getYVal());
player.isJumping = true;
run1Resized = Bitmap.createScaledBitmap(playerJumpImage, 200, 200, false);
}
else if(player.getJumpCount() < 1)
{
player.setYval(player.getYVal());
player.incrJump();
player.isJumping = true;
run1Resized = Bitmap.createScaledBitmap(playerJumpImage, 200, 200, false);
}
} else {
fireball.setOnScreen(true);
}
}
return true;
}
/**
* Method that pauses the game when necessary (i.e when home button is pressed)
*/
public void pause() {
isPlaying = false;
backgroundMusic.pause();
try {
gameThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Resumes the game after a pause
*/
public void resume() {
isPlaying = true;
backgroundMusic.start();
gameThread = new Thread(this);
gameThread.start();
}
}
Thank you for your time & any help is appreciated!
EDIT: Also using bitmaps to redraw etc. as that is what I am used to.

How to run SoundPool in Android Studio on actions

I am new to working in Android Studio and only average with java, thus far. I am working on an assignment for school, but I am having problems getting SoundPool to work without crashing the application. I know the app runs without the sound I have added, but I am uncertain where my error lies as there seems to be so many different ways to approach this problem.
The error I am getting in debug is:
java.lang.NullPointerException: Attempt to invoke virtual method 'int
android.media.SoundPool.play(int, float, float, int, int, float)' on a
null object reference
My code is below.
public class Snake extends Activity {
private SnakeView mSnakeView;
private static String ICICLE_KEY = "snake-view";
/**
* Called when Activity is first created. Turns off the title bar, sets up
* the content views, and fires up the SnakeView.
*
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//set the layout
setContentView(R.layout.snake_layout);
mSnakeView = (SnakeView) findViewById(R.id.snake);
mSnakeView.setTextView((TextView) findViewById(R.id.text));
if (savedInstanceState == null) {
// We were just launched -- set up a new game
mSnakeView.setMode(SnakeView.READY);
} else {
// We are being restored
Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
if (map != null) {
mSnakeView.restoreState(map);
} else {
mSnakeView.setMode(SnakeView.PAUSE);
}
}
}
#Override
protected void onPause() {
super.onPause();
// Pause the game along with the activity
mSnakeView.setMode(SnakeView.PAUSE);
}
#Override
public void onSaveInstanceState(Bundle outState) {
//Store the game state
outState.putBundle(ICICLE_KEY, mSnakeView.saveState());
}
public class SnakeView extends TileView {
private static final String TAG = "SnakeView";
private Context m_context;
//Sound
//initialize sound variables
private SoundPool sounds;
private int ulose = -1;
private int apple = -1;
private int moving = -1;
/**
* Current mode of application: READY to run, RUNNING, or you have already
* lost. static final ints are used instead of an enum for performance
* reasons.
*/
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;
/**
* Current direction the snake is headed.
*/
private int mDirection = NORTH;
private int mNextDirection = NORTH;
private static final int NORTH = 1;
private static final int SOUTH = 2;
private static final int EAST = 3;
private static final int WEST = 4;
/**
* Labels for the drawables that will be loaded into the TileView class
*/
private static final int RED_STAR = 1;
private static final int YELLOW_STAR = 2;
private static final int GREEN_STAR = 3;
/**
* mScore: used to track the number of apples captured mMoveDelay: number of
* milliseconds between snake movements. This will decrease as apples are
* captured.
*/
private long mScore = 0;
private long mMoveDelay = 600;
/**
* mLastMove: tracks the absolute time when the snake last moved, and is used
* to determine if a move should be made based on mMoveDelay.
*/
private long mLastMove;
/**
* mStatusText: text shows to the user in some run states
*/
private TextView mStatusText;
/**
* mSnakeTrail: a list of Coordinates that make up the snake's body
* mAppleList: the secret location of the juicy apples the snake craves.
*/
private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();
private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();
/**
* Everyone needs a little randomness in their life
*/
private static final Random RNG = new Random();
/**
* Create a simple handler that we can use to cause animation to happen. We
* set ourselves as a target and we can use the sleep()
* function to cause an update/invalidate to occur at a later date.
*/
private RefreshHandler mRedrawHandler = new RefreshHandler();
class RefreshHandler extends Handler {
#Override
public void handleMessage(Message msg) {
SnakeView.this.update();
SnakeView.this.invalidate();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
};
/**
* Constructs a SnakeView based on inflation from XML
*
* #param context
* #param attrs
*/
public SnakeView(Context context, AttributeSet attrs) {
super(context, attrs);
initSnakeView();
}
public SnakeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initSnakeView();
}
private void initSnakeView() {
setFocusable(true);
Resources r = this.getContext().getResources();
resetTiles(4);
loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));
}
private void initNewGame() {
mSnakeTrail.clear();
mAppleList.clear();
// For now we're just going to load up a short default eastbound snake
// that's just turned north
mSnakeTrail.add(new Coordinate(7, 7));
mSnakeTrail.add(new Coordinate(6, 7));
mSnakeTrail.add(new Coordinate(5, 7));
mSnakeTrail.add(new Coordinate(4, 7));
mSnakeTrail.add(new Coordinate(3, 7));
mSnakeTrail.add(new Coordinate(2, 7));
mNextDirection = NORTH;
// Two apples to start with
addRandomApple();
addRandomApple();
mMoveDelay = 600;
mScore = 0;
}
/**
* Given a ArrayList of coordinates, we need to flatten them into an array of
* ints before we can stuff them into a map for flattening and storage.
*
* #param cvec : a ArrayList of Coordinate objects
* #return : a simple array containing the x/y values of the coordinates
* as [x1,y1,x2,y2,x3,y3...]
*/
private int[] coordArrayListToArray(ArrayList<Coordinate> cvec) {
int count = cvec.size();
int[] rawArray = new int[count * 2];
for (int index = 0; index < count; index++) {
Coordinate c = cvec.get(index);
rawArray[2 * index] = c.x;
rawArray[2 * index + 1] = c.y;
}
return rawArray;
}
/**
* Save game state so that the user does not lose anything
* if the game process is killed while we are in the
* background.
*
* #return a Bundle with this view's state
*/
public Bundle saveState() {
Bundle map = new Bundle();
map.putIntArray("mAppleList", coordArrayListToArray(mAppleList));
map.putInt("mDirection", Integer.valueOf(mDirection));
map.putInt("mNextDirection", Integer.valueOf(mNextDirection));
map.putLong("mMoveDelay", Long.valueOf(mMoveDelay));
map.putLong("mScore", Long.valueOf(mScore));
map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail));
return map;
}
/**
* Given a flattened array of ordinate pairs, we reconstitute them into a
* ArrayList of Coordinate objects
*
* #param rawArray : [x1,y1,x2,y2,...]
* #return a ArrayList of Coordinates
*/
private ArrayList<Coordinate> coordArrayToArrayList(int[] rawArray) {
ArrayList<Coordinate> coordArrayList = new ArrayList<Coordinate>();
int coordCount = rawArray.length;
for (int index = 0; index < coordCount; index += 2) {
Coordinate c = new Coordinate(rawArray[index], rawArray[index + 1]);
coordArrayList.add(c);
}
return coordArrayList;
}
/**
* Restore game state if our process is being relaunched
*
* #param icicle a Bundle containing the game state
*/
public void restoreState(Bundle icicle) {
setMode(PAUSE);
mAppleList = coordArrayToArrayList(icicle.getIntArray("mAppleList"));
mDirection = icicle.getInt("mDirection");
mNextDirection = icicle.getInt("mNextDirection");
mMoveDelay = icicle.getLong("mMoveDelay");
mScore = icicle.getLong("mScore");
mSnakeTrail = coordArrayToArrayList(icicle.getIntArray("mSnakeTrail"));
}
/*
* handles key events in the game. Update the direction our snake is traveling
* based on the DPAD. Ignore events that would cause the snake to immediately
* turn back on itself.
*
* (non-Javadoc)
*
* #see android.view.View#onKeyDown(int, android.os.KeyEvent)
*/
#Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
if (mMode == READY | mMode == LOSE) {
/*
* At the beginning of the game, or the end of a previous one,
* we should start a new game.
*/
initNewGame();
setMode(RUNNING);
update();
return (true);
}
if (mMode == PAUSE) {
/*
* If the game is merely paused, we should just continue where
* we left off.
*/
setMode(RUNNING);
update();
return (true);
}
if (mDirection != SOUTH) {
mNextDirection = NORTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
if (mDirection != NORTH) {
mNextDirection = SOUTH;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
if (mDirection != EAST) {
mNextDirection = WEST;
}
return (true);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
if (mDirection != WEST) {
mNextDirection = EAST;
}
return (true);
}
return super.onKeyDown(keyCode, msg);
}
public void loadSound(){
sounds = new SoundPool(3, AudioManager.STREAM_MUSIC, 0);
try{
//Create objects for the required classes
AssetManager assetManager = m_context.getAssets();
AssetFileDescriptor descriptor;
//Create the three sound effects in memory
descriptor = assetManager.openFd("wall.ogg");
ulose = sounds.load(descriptor, 0);
descriptor = assetManager.openFd("apple.ogg");
apple = sounds.load(descriptor, 0);
descriptor = assetManager.openFd("tiles.ogg");
moving = sounds.load(descriptor, 0);
}
catch (IOException e){
Log.e("error", "failed to load sound files");
}
}
/**
* Sets the TextView that will be used to give information (such as "Game
* Over" to the user.
*
* #param newView
*/
public void setTextView(TextView newView) {
mStatusText = newView;
}
/**
* Updates the current mode of the application (RUNNING or PAUSED or the like)
* as well as sets the visibility of textview for notification
*
* #param newMode
*/
public void setMode(int newMode) {
int oldMode = mMode;
mMode = newMode;
if (newMode == RUNNING & oldMode != RUNNING) {
mStatusText.setVisibility(View.INVISIBLE);
update();
return;
}
Resources res = getContext().getResources();
CharSequence str = "";
if (newMode == PAUSE) {
str = res.getText(R.string.mode_pause);
}
if (newMode == READY) {
str = res.getText(R.string.mode_ready);
}
if (newMode == LOSE) {
str = res.getString(R.string.mode_lose_prefix) + mScore
+ res.getString(R.string.mode_lose_suffix);
}
mStatusText.setText(str);
mStatusText.setVisibility(View.VISIBLE);
}
/**
* Selects a random location within the garden that is not currently covered
* by the snake. Currently _could_ go into an infinite loop if the snake
* currently fills the garden, but we'll leave discovery of this prize to a
* truly excellent snake-player.
*
*/
private void addRandomApple() {
Coordinate newCoord = null;
boolean found = false;
while (!found) {
// Choose a new location for our apple
int newX = 1 + RNG.nextInt(mXTileCount - 2);
int newY = 1 + RNG.nextInt(mYTileCount - 2);
newCoord = new Coordinate(newX, newY);
// Make sure it's not already under the snake
boolean collision = false;
int snakelength = mSnakeTrail.size();
for (int index = 0; index < snakelength; index++) {
if (mSnakeTrail.get(index).equals(newCoord)) {
collision = true;
}
}
// if we're here and there's been no collision, then we have
// a good location for an apple. Otherwise, we'll circle back
// and try again
found = !collision;
}
if (newCoord == null) {
Log.e(TAG, "Somehow ended up with a null newCoord!");
}
mAppleList.add(newCoord);
}
/**
* Handles the basic update loop, checking to see if we are in the running
* state, determining if a move should be made, updating the snake's location.
*/
public void update() {
if (mMode == RUNNING) {
long now = System.currentTimeMillis();
if (now - mLastMove > mMoveDelay) {
clearTiles();
updateWalls();
updateSnake();
updateApples();
mLastMove = now;
}
mRedrawHandler.sleep(mMoveDelay);
}
}
/**
* Draws some walls.
*
*/
private void updateWalls() {
for (int x = 0; x < mXTileCount; x++) {
setTile(GREEN_STAR, x, 0);
setTile(GREEN_STAR, x, mYTileCount - 1);
}
for (int y = 1; y < mYTileCount - 1; y++) {
setTile(GREEN_STAR, 0, y);
setTile(GREEN_STAR, mXTileCount - 1, y);
}
}
/**
* Draws some apples.
*
*/
private void updateApples() {
for (Coordinate c : mAppleList) {
setTile(YELLOW_STAR, c.x, c.y);
}
}
/**
* Figure out which way the snake is going, see if he's run into anything (the
* walls, himself, or an apple). If he's not going to die, we then add to the
* front and subtract from the rear in order to simulate motion. If we want to
* grow him, we don't subtract from the rear.
*
*/
private void updateSnake() {
boolean growSnake = false;
// grab the snake by the head
Coordinate head = mSnakeTrail.get(0);
Coordinate newHead = new Coordinate(1, 1);
mDirection = mNextDirection;
switch (mDirection) {
case EAST: {
newHead = new Coordinate(head.x + 1, head.y);
sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
break;
}
case WEST: {
newHead = new Coordinate(head.x - 1, head.y);
sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
break;
}
case NORTH: {
newHead = new Coordinate(head.x, head.y - 1);
sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
break;
}
case SOUTH: {
newHead = new Coordinate(head.x, head.y + 1);
sounds.play(moving, 1.0f, 1.0f, 0, 0, 1.5f);
break;
}
}
// Collision detection
// For now we have a 1-square wall around the entire arena
if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
|| (newHead.y > mYTileCount - 2)) {
sounds.play(ulose, 1.0f, 1.0f, 0, 0, 1.5f);
setMode(LOSE);
return;
}
// Look for collisions with itself
int snakelength = mSnakeTrail.size();
for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
Coordinate c = mSnakeTrail.get(snakeindex);
if (c.equals(newHead)) {
sounds.play(ulose, 1.0f, 1.0f, 0, 0, 1.5f);
setMode(LOSE);
return;
}
}
// Look for apples
int applecount = mAppleList.size();
for (int appleindex = 0; appleindex < applecount; appleindex++) {
Coordinate c = mAppleList.get(appleindex);
if (c.equals(newHead)) {
sounds.play(apple, 1.0f, 1.0f, 0, 0, 1.5f);
mAppleList.remove(c);
addRandomApple();
mScore++;
mMoveDelay *= 0.9;
growSnake = true;
}
}
// push a new head onto the ArrayList and pull off the tail
mSnakeTrail.add(0, newHead);
// except if we want the snake to grow
if (!growSnake) {
mSnakeTrail.remove(mSnakeTrail.size() - 1);
}
int index = 0;
for (Coordinate c : mSnakeTrail) {
if (index == 0) {
setTile(YELLOW_STAR, c.x, c.y);
} else {
setTile(RED_STAR, c.x, c.y);
}
index++;
}
}
/**
* Simple class containing two integer values and a comparison function.
* There's probably something I should use instead, but this was quick and
* easy to build.
*
*/
private class Coordinate {
public int x;
public int y;
public Coordinate(int newX, int newY) {
x = newX;
y = newY;
}
public boolean equals(Coordinate other) {
if (x == other.x && y == other.y) {
return true;
}
return false;
}
#Override
public String toString() {
return "Coordinate: [" + x + "," + y + "]";
}
}
}
I am not looking for someone to do my work for me, I just want to be able to understand where I have made mistakes and the proper approach to correct them so I can learn from this as intended. Any suggestions are greatly appreciated.
(1) You don't ever seem to call the method loadSound().
You don't instantiate the soundPool object, that is the error you are getting. You need to call the loadSound() method.
(2) Your not setting m_context to a value, do this:
m_context = getApplicationContext();
(3) There is an easier way to load sounds from the raw directory:
apple = soundsload(getApplicationContext(),R.raw.apple,1);

The price bar with max and min prices with android

How can i do this with android (java)?
You can use below Range-Seek-Bar
check below class souce code available here
public class RangeSeekBar<T extends Number> extends ImageView {
public static final Integer DEFAULT_MINIMUM = 0;
public static final Integer DEFAULT_MAXIMUM = 100;
public static final int HEIGHT_IN_DP = 30;
public static final int TEXT_LATERAL_PADDING_IN_DP = 3;
private static final int INITIAL_PADDING_IN_DP = 8;
private final int LINE_HEIGHT_IN_DP = 1;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Bitmap thumbImage = BitmapFactory.decodeResource(getResources(), R.drawable.seek_thumb_normal);
private final Bitmap thumbPressedImage = BitmapFactory.decodeResource(getResources(),
R.drawable.seek_thumb_pressed);
private final Bitmap thumbDisabledImage = BitmapFactory.decodeResource(getResources(),
R.drawable.seek_thumb_disabled);
private final float thumbWidth = thumbImage.getWidth();
private final float thumbHalfWidth = 0.5f * thumbWidth;
private final float thumbHalfHeight = 0.5f * thumbImage.getHeight();
private float INITIAL_PADDING;
private float padding;
private T absoluteMinValue, absoluteMaxValue;
private NumberType numberType;
private double absoluteMinValuePrim, absoluteMaxValuePrim;
private double normalizedMinValue = 0d;
private double normalizedMaxValue = 1d;
private Thumb pressedThumb = null;
private boolean notifyWhileDragging = false;
private OnRangeSeekBarChangeListener<T> listener;
/**
* Default color of a {#link RangeSeekBar}, #FF33B5E5. This is also known as "Ice Cream Sandwich" blue.
*/
public static final int DEFAULT_COLOR = Color.argb(0xFF, 0x33, 0xB5, 0xE5);
/**
* An invalid pointer id.
*/
public static final int INVALID_POINTER_ID = 255;
// Localized constants from MotionEvent for compatibility
// with API < 8 "Froyo".
public static final int ACTION_POINTER_UP = 0x6, ACTION_POINTER_INDEX_MASK = 0x0000ff00, ACTION_POINTER_INDEX_SHIFT = 8;
private float mDownMotionX;
private int mActivePointerId = INVALID_POINTER_ID;
private int mScaledTouchSlop;
private boolean mIsDragging;
private int mTextOffset;
private int mTextSize;
private int mDistanceToTop;
private RectF mRect;
private static final int DEFAULT_TEXT_SIZE_IN_DP = 14;
private static final int DEFAULT_TEXT_DISTANCE_TO_BUTTON_IN_DP = 8;
private static final int DEFAULT_TEXT_DISTANCE_TO_TOP_IN_DP = 8;
private boolean mSingleThumb;
public RangeSeekBar(Context context) {
super(context);
init(context, null);
}
public RangeSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RangeSeekBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private T extractNumericValueFromAttributes(TypedArray a, int attribute, int defaultValue) {
TypedValue tv = a.peekValue(attribute);
if (tv == null) {
return (T) Integer.valueOf(defaultValue);
}
int type = tv.type;
if (type == TypedValue.TYPE_FLOAT) {
return (T) Float.valueOf(a.getFloat(attribute, defaultValue));
} else {
return (T) Integer.valueOf(a.getInteger(attribute, defaultValue));
}
}
private void init(Context context, AttributeSet attrs) {
if (attrs == null) {
setRangeToDefaultValues();
} else {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RangeSeekBar, 0, 0);
setRangeValues(
extractNumericValueFromAttributes(a, R.styleable.RangeSeekBar_absoluteMinValue, DEFAULT_MINIMUM),
extractNumericValueFromAttributes(a, R.styleable.RangeSeekBar_absoluteMaxValue, DEFAULT_MAXIMUM));
mSingleThumb = a.getBoolean(R.styleable.RangeSeekBar_singleThumb, false);
a.recycle();
}
setValuePrimAndNumberType();
INITIAL_PADDING = PixelUtil.dpToPx(context, INITIAL_PADDING_IN_DP);
mTextSize = PixelUtil.dpToPx(context, DEFAULT_TEXT_SIZE_IN_DP);
mDistanceToTop = PixelUtil.dpToPx(context, DEFAULT_TEXT_DISTANCE_TO_TOP_IN_DP);
mTextOffset = this.mTextSize + PixelUtil.dpToPx(context,
DEFAULT_TEXT_DISTANCE_TO_BUTTON_IN_DP) + this.mDistanceToTop;
float lineHeight = PixelUtil.dpToPx(context, LINE_HEIGHT_IN_DP);
mRect = new RectF(padding,
mTextOffset + thumbHalfHeight - lineHeight / 2,
getWidth() - padding,
mTextOffset + thumbHalfHeight + lineHeight / 2);
// make RangeSeekBar focusable. This solves focus handling issues in case EditText widgets are being used along with the RangeSeekBar within ScollViews.
setFocusable(true);
setFocusableInTouchMode(true);
mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
public void setRangeValues(T minValue, T maxValue) {
this.absoluteMinValue = minValue;
this.absoluteMaxValue = maxValue;
setValuePrimAndNumberType();
}
#SuppressWarnings("unchecked")
// only used to set default values when initialised from XML without any values specified
private void setRangeToDefaultValues() {
this.absoluteMinValue = (T) DEFAULT_MINIMUM;
this.absoluteMaxValue = (T) DEFAULT_MAXIMUM;
setValuePrimAndNumberType();
}
private void setValuePrimAndNumberType() {
absoluteMinValuePrim = absoluteMinValue.doubleValue();
absoluteMaxValuePrim = absoluteMaxValue.doubleValue();
numberType = NumberType.fromNumber(absoluteMinValue);
}
public void resetSelectedValues() {
setSelectedMinValue(absoluteMinValue);
setSelectedMaxValue(absoluteMaxValue);
}
public boolean isNotifyWhileDragging() {
return notifyWhileDragging;
}
/**
* Should the widget notify the listener callback while the user is still dragging a thumb? Default is false.
*
* #param flag
*/
public void setNotifyWhileDragging(boolean flag) {
this.notifyWhileDragging = flag;
}
/**
* Returns the absolute minimum value of the range that has been set at construction time.
*
* #return The absolute minimum value of the range.
*/
public T getAbsoluteMinValue() {
return absoluteMinValue;
}
/**
* Returns the absolute maximum value of the range that has been set at construction time.
*
* #return The absolute maximum value of the range.
*/
public T getAbsoluteMaxValue() {
return absoluteMaxValue;
}
/**
* Returns the currently selected min value.
*
* #return The currently selected min value.
*/
public T getSelectedMinValue() {
return normalizedToValue(normalizedMinValue);
}
/**
* Sets the currently selected minimum value. The widget will be invalidated and redrawn.
*
* #param value The Number value to set the minimum value to. Will be clamped to given absolute minimum/maximum range.
*/
public void setSelectedMinValue(T value) {
// in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
setNormalizedMinValue(0d);
} else {
setNormalizedMinValue(valueToNormalized(value));
}
}
/**
* Returns the currently selected max value.
*
* #return The currently selected max value.
*/
public T getSelectedMaxValue() {
return normalizedToValue(normalizedMaxValue);
}
/**
* Sets the currently selected maximum value. The widget will be invalidated and redrawn.
*
* #param value The Number value to set the maximum value to. Will be clamped to given absolute minimum/maximum range.
*/
public void setSelectedMaxValue(T value) {
// in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
setNormalizedMaxValue(1d);
} else {
setNormalizedMaxValue(valueToNormalized(value));
}
}
/**
* Registers given listener callback to notify about changed selected values.
*
* #param listener The listener to notify about changed selected values.
*/
public void setOnRangeSeekBarChangeListener(OnRangeSeekBarChangeListener<T> listener) {
this.listener = listener;
}
/**
* Handles thumb selection and movement. Notifies listener callback on certain events.
*/
#Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
int pointerIndex;
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
// Remember where the motion event started
mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
pointerIndex = event.findPointerIndex(mActivePointerId);
mDownMotionX = event.getX(pointerIndex);
pressedThumb = evalPressedThumb(mDownMotionX);
// Only handle thumb presses.
if (pressedThumb == null) {
return super.onTouchEvent(event);
}
setPressed(true);
invalidate();
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
break;
case MotionEvent.ACTION_MOVE:
if (pressedThumb != null) {
if (mIsDragging) {
trackTouchEvent(event);
} else {
// Scroll to follow the motion event
pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {
setPressed(true);
invalidate();
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
}
}
if (notifyWhileDragging && listener != null) {
listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue());
}
}
break;
case MotionEvent.ACTION_UP:
if (mIsDragging) {
trackTouchEvent(event);
onStopTrackingTouch();
setPressed(false);
} else {
// Touch up when we never crossed the touch slop threshold
// should be interpreted as a tap-seek to that location.
onStartTrackingTouch();
trackTouchEvent(event);
onStopTrackingTouch();
}
pressedThumb = null;
invalidate();
if (listener != null) {
listener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue());
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = event.getPointerCount() - 1;
// final int index = ev.getActionIndex();
mDownMotionX = event.getX(index);
mActivePointerId = event.getPointerId(index);
invalidate();
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(event);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
if (mIsDragging) {
onStopTrackingTouch();
setPressed(false);
}
invalidate(); // see above explanation
break;
}
return true;
}
private final void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose
// a new active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mDownMotionX = ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
}
}
private final void trackTouchEvent(MotionEvent event) {
final int pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
if (Thumb.MIN.equals(pressedThumb) && !mSingleThumb) {
setNormalizedMinValue(screenToNormalized(x));
} else if (Thumb.MAX.equals(pressedThumb)) {
setNormalizedMaxValue(screenToNormalized(x));
}
}
/**
* Tries to claim the user's drag motion, and requests disallowing any ancestors from stealing events in the drag.
*/
private void attemptClaimDrag() {
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
/**
* This is called when the user has started touching this widget.
*/
void onStartTrackingTouch() {
mIsDragging = true;
}
/**
* This is called when the user either releases his touch or the touch is canceled.
*/
void onStopTrackingTouch() {
mIsDragging = false;
}
/**
* Ensures correct size of the widget.
*/
#Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 200;
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
width = MeasureSpec.getSize(widthMeasureSpec);
}
int height = thumbImage.getHeight() + PixelUtil.dpToPx(getContext(), HEIGHT_IN_DP);
if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
}
setMeasuredDimension(width, height);
}
/**
* Draws the widget on the given canvas.
*/
#Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setTextSize(mTextSize);
paint.setStyle(Style.FILL);
paint.setColor(Color.GRAY);
paint.setAntiAlias(true);
// draw min and max labels
String minLabel = getContext().getString(R.string.demo_min_label);
String maxLabel = getContext().getString(R.string.demo_max_label);
float minMaxLabelSize = Math.max(paint.measureText(minLabel), paint.measureText(maxLabel));
float minMaxHeight = mTextOffset + thumbHalfHeight + mTextSize / 3;
canvas.drawText(minLabel, 0, minMaxHeight, paint);
canvas.drawText(maxLabel, getWidth() - minMaxLabelSize, minMaxHeight, paint);
padding = INITIAL_PADDING + minMaxLabelSize + thumbHalfWidth;
// draw seek bar background line
mRect.left = padding;
mRect.right = getWidth() - padding;
canvas.drawRect(mRect, paint);
boolean selectedValuesAreDefault = (getSelectedMinValue().equals(getAbsoluteMinValue()) &&
getSelectedMaxValue().equals(getAbsoluteMaxValue()));
int colorToUseForButtonsAndHighlightedLine = selectedValuesAreDefault ?
Color.GRAY : // default values
DEFAULT_COLOR; //non default, filter is active
// draw seek bar active range line
mRect.left = normalizedToScreen(normalizedMinValue);
mRect.right = normalizedToScreen(normalizedMaxValue);
paint.setColor(colorToUseForButtonsAndHighlightedLine);
canvas.drawRect(mRect, paint);
// draw minimum thumb if not a single thumb control
if (!mSingleThumb) {
drawThumb(normalizedToScreen(normalizedMinValue), Thumb.MIN.equals(pressedThumb), canvas,
selectedValuesAreDefault);
}
// draw maximum thumb
drawThumb(normalizedToScreen(normalizedMaxValue), Thumb.MAX.equals(pressedThumb), canvas,
selectedValuesAreDefault);
// draw the text if sliders have moved from default edges
if (!selectedValuesAreDefault) {
paint.setTextSize(mTextSize);
paint.setColor(Color.WHITE);
// give text a bit more space here so it doesn't get cut off
int offset = PixelUtil.dpToPx(getContext(), TEXT_LATERAL_PADDING_IN_DP);
String minText = String.valueOf(getSelectedMinValue());
String maxText = String.valueOf(getSelectedMaxValue());
float minTextWidth = paint.measureText(minText) + offset;
float maxTextWidth = paint.measureText(maxText) + offset;
if (!mSingleThumb) {
canvas.drawText(minText,
normalizedToScreen(normalizedMinValue) - minTextWidth * 0.5f,
mDistanceToTop + mTextSize,
paint);
}
canvas.drawText(maxText,
normalizedToScreen(normalizedMaxValue) - maxTextWidth * 0.5f,
mDistanceToTop + mTextSize,
paint);
}
}
/**
* Overridden to save instance state when device orientation changes. This method is called automatically if you assign an id to the RangeSeekBar widget using the {#link #setId(int)} method. Other members of this class than the normalized min and max values don't need to be saved.
*/
#Override
protected Parcelable onSaveInstanceState() {
final Bundle bundle = new Bundle();
bundle.putParcelable("SUPER", super.onSaveInstanceState());
bundle.putDouble("MIN", normalizedMinValue);
bundle.putDouble("MAX", normalizedMaxValue);
return bundle;
}
/**
* Overridden to restore instance state when device orientation changes. This method is called automatically if you assign an id to the RangeSeekBar widget using the {#link #setId(int)} method.
*/
#Override
protected void onRestoreInstanceState(Parcelable parcel) {
final Bundle bundle = (Bundle) parcel;
super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
normalizedMinValue = bundle.getDouble("MIN");
normalizedMaxValue = bundle.getDouble("MAX");
}
/**
* Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
*
* #param screenCoord The x-coordinate in screen space where to draw the image.
* #param pressed Is the thumb currently in "pressed" state?
* #param canvas The canvas to draw upon.
*/
private void drawThumb(float screenCoord, boolean pressed, Canvas canvas, boolean areSelectedValuesDefault) {
Bitmap buttonToDraw;
if (areSelectedValuesDefault) {
buttonToDraw = thumbDisabledImage;
} else {
buttonToDraw = pressed ? thumbPressedImage : thumbImage;
}
canvas.drawBitmap(buttonToDraw, screenCoord - thumbHalfWidth,
mTextOffset,
paint);
}
/**
* Decides which (if any) thumb is touched by the given x-coordinate.
*
* #param touchX The x-coordinate of a touch event in screen space.
* #return The pressed thumb or null if none has been touched.
*/
private Thumb evalPressedThumb(float touchX) {
Thumb result = null;
boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue);
boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue);
if (minThumbPressed && maxThumbPressed) {
// if both thumbs are pressed (they lie on top of each other), choose the one with more room to drag. this avoids "stalling" the thumbs in a corner, not being able to drag them apart anymore.
result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
} else if (minThumbPressed) {
result = Thumb.MIN;
} else if (maxThumbPressed) {
result = Thumb.MAX;
}
return result;
}
/**
* Decides if given x-coordinate in screen space needs to be interpreted as "within" the normalized thumb x-coordinate.
*
* #param touchX The x-coordinate in screen space to check.
* #param normalizedThumbValue The normalized x-coordinate of the thumb to check.
* #return true if x-coordinate is in thumb range, false otherwise.
*/
private boolean isInThumbRange(float touchX, double normalizedThumbValue) {
return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth;
}
/**
* Sets normalized min value to value so that 0 <= value <= normalized max value <= 1. The View will get invalidated when calling this method.
*
* #param value The new normalized min value to set.
*/
private void setNormalizedMinValue(double value) {
normalizedMinValue = Math.max(0d, Math.min(1d, Math.min(value, normalizedMaxValue)));
invalidate();
}
/**
* Sets normalized max value to value so that 0 <= normalized min value <= value <= 1. The View will get invalidated when calling this method.
*
* #param value The new normalized max value to set.
*/
private void setNormalizedMaxValue(double value) {
normalizedMaxValue = Math.max(0d, Math.min(1d, Math.max(value, normalizedMinValue)));
invalidate();
}
/**
* Converts a normalized value to a Number object in the value space between absolute minimum and maximum.
*
* #param normalized
* #return
*/
#SuppressWarnings("unchecked")
private T normalizedToValue(double normalized) {
double v = absoluteMinValuePrim + normalized * (absoluteMaxValuePrim - absoluteMinValuePrim);
// TODO parameterize this rounding to allow variable decimal points
return (T) numberType.toNumber(Math.round(v * 100) / 100d);
}
/**
* Converts the given Number value to a normalized double.
*
* #param value The Number value to normalize.
* #return The normalized double.
*/
private double valueToNormalized(T value) {
if (0 == absoluteMaxValuePrim - absoluteMinValuePrim) {
// prevent division by zero, simply return 0.
return 0d;
}
return (value.doubleValue() - absoluteMinValuePrim) / (absoluteMaxValuePrim - absoluteMinValuePrim);
}
/**
* Converts a normalized value into screen space.
*
* #param normalizedCoord The normalized value to convert.
* #return The converted value in screen space.
*/
private float normalizedToScreen(double normalizedCoord) {
return (float) (padding + normalizedCoord * (getWidth() - 2 * padding));
}
/**
* Converts screen space x-coordinates into normalized values.
*
* #param screenCoord The x-coordinate in screen space to convert.
* #return The normalized value.
*/
private double screenToNormalized(float screenCoord) {
int width = getWidth();
if (width <= 2 * padding) {
// prevent division by zero, simply return 0.
return 0d;
} else {
double result = (screenCoord - padding) / (width - 2 * padding);
return Math.min(1d, Math.max(0d, result));
}
}
/**
* Callback listener interface to notify about changed range values.
*
* #param <T> The Number type the RangeSeekBar has been declared with.
* #author Stephan Tittel (stephan.tittel#kom.tu-darmstadt.de)
*/
public interface OnRangeSeekBarChangeListener<T> {
public void onRangeSeekBarValuesChanged(RangeSeekBar<?> bar, T minValue, T maxValue);
}
/**
* Thumb constants (min and max).
*/
private static enum Thumb {
MIN, MAX
}
;
/**
* Utility enumeration used to convert between Numbers and doubles.
*
* #author Stephan Tittel (stephan.tittel#kom.tu-darmstadt.de)
*/
private static enum NumberType {
LONG, DOUBLE, INTEGER, FLOAT, SHORT, BYTE, BIG_DECIMAL;
public static <E extends Number> NumberType fromNumber(E value) throws IllegalArgumentException {
if (value instanceof Long) {
return LONG;
}
if (value instanceof Double) {
return DOUBLE;
}
if (value instanceof Integer) {
return INTEGER;
}
if (value instanceof Float) {
return FLOAT;
}
if (value instanceof Short) {
return SHORT;
}
if (value instanceof Byte) {
return BYTE;
}
if (value instanceof BigDecimal) {
return BIG_DECIMAL;
}
throw new IllegalArgumentException("Number class '" + value.getClass().getName() + "' is not supported");
}
public Number toNumber(double value) {
switch (this) {
case LONG:
return Long.valueOf((long) value);
case DOUBLE:
return value;
case INTEGER:
return Integer.valueOf((int) value);
case FLOAT:
return Float.valueOf((float)value);
case SHORT:
return Short.valueOf((short) value);
case BYTE:
return Byte.valueOf((byte) value);
case BIG_DECIMAL:
return BigDecimal.valueOf(value);
}
throw new InstantiationError("can't convert " + this + " to a Number object");
}
}
}
Ok, you can use the predefined library to acheive that, there will two Thumbs and you'll get left and right thumb value.
Use this Material Range Bar

Drawing a hero and moving it

What is the best way drawing a hero and moving it? I just need the best code for doing that. Before writing this i found a way, but when i made the surface holder transparent, i realised that the code is drawing new bitmap in the front of the old one every milisecond. That way looks kind of laggy to me, but maymie i'm not right. Please help me. Actualy i'm kind of confused...
Anyway, here's the code that i think is laggy:
MainThread.java
/**
*
*/
package com.workspace.pockethero;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
/**
* #author impaler
*
* The Main thread which contains the game loop. The thread must have access to
* the surface view and holder to trigger events every game tick.
*/
public class MainThread extends Thread {
private static final String TAG = MainThread.class.getSimpleName();
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private MainGamePanel gamePanel;
// flag to hold game state
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run() {
Canvas canvas;
canvas = this.surfaceHolder.lockCanvas();
Log.d(TAG, "Starting game loop");
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
synchronized (surfaceHolder) {
// update game state
this.gamePanel.update();
// render state to the screen
// draws the canvas on the panel
this.gamePanel.render(canvas);
}
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
}
MainGamePanel.java
/**
*
*/
package com.workspace.pockethero;
import com.workspace.pockethero.model.Droid;
import com.workspace.pockethero.buttons.*;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
/**
* #author impaler
* This is the main surface that handles the ontouch events and draws
* the image to the screen.
*/
public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {
private static final String TAG = MainGamePanel.class.getSimpleName();
private MainThread thread;
public Droid droid;
public Butt butt;
public Butt1 butt1;
public Butt2 butt2;
public Butt3 butt3;
public Buttz buttz;
public Buttz1 buttz1;
public Buttz2 buttz2;
public Buttz3 buttz3;
public Buttx buttx;
public Build build;
public int decentreX;
public int decentreY;
public int debottomA;
public boolean moved;
public boolean moved1;
public boolean moved2;
public boolean moved3;
public boolean moved4;
public boolean moved5;
public boolean moved6;
public boolean moved7;
private Drawable myImage;
public boolean mapPainted;
public MainGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create droid and load bitmap
decentreX = PocketHero.centreX;
decentreY = PocketHero.centreY;
debottomA = PocketHero.bottomA;
droid = new Droid(BitmapFactory.decodeResource(getResources(), R.drawable.herod), decentreX, decentreY);
butt = new Butt(BitmapFactory.decodeResource(getResources(), R.drawable.button), 110, debottomA - 70);
butt1 = new Butt1(BitmapFactory.decodeResource(getResources(), R.drawable.button1), 70, debottomA - 110);
butt2 = new Butt2(BitmapFactory.decodeResource(getResources(), R.drawable.button2), 30, debottomA - 70);
butt3 = new Butt3(BitmapFactory.decodeResource(getResources(), R.drawable.button3), 70, debottomA - 30);
buttz = new Buttz(BitmapFactory.decodeResource(getResources(), R.drawable.zbutton), 110, debottomA - 110);
buttz1 = new Buttz1(BitmapFactory.decodeResource(getResources(), R.drawable.zbutton1), 30, debottomA - 110);
buttz2 = new Buttz2(BitmapFactory.decodeResource(getResources(), R.drawable.zbutton2), 30, debottomA - 30);
buttz3 = new Buttz3(BitmapFactory.decodeResource(getResources(), R.drawable.zbutton3), 110, debottomA - 30);
buttx = new Buttx(BitmapFactory.decodeResource(getResources(), R.drawable.xbutton), 70, debottomA - 70);
build = new Build(BitmapFactory.decodeResource(getResources(), R.drawable.building), 500, 200);
// create the game loop thread
//300 indicates start position of bitmapfield on screen
thread = new MainThread(getHolder(), this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
this.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View V, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// delegating event handling to the droid
handleActionDown((int)event.getX(), (int)event.getY());
} if (event.getAction() == MotionEvent.ACTION_MOVE) {
handleActionDown((int)event.getX(), (int)event.getY());
// the gestures
} if (event.getAction() == MotionEvent.ACTION_UP) {
// touch was released
if (droid.touched) {
droid.setTouched(false);
}
if (droid.touched1) {
droid.setTouched1(false);
}
if (droid.touched2) {
droid.setTouched2(false);
}
if (droid.touched3) {
droid.setTouched3(false);
}
}
return true;
}
});
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
public void surfaceCreated(SurfaceHolder holder) {
// at this point the surface is created and
// we can safely start the game loop
thread.setRunning(true);
thread.start();
}
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface is being destroyed");
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
public void render(Canvas canvas) {
canvas.drawColor(Color.TRANSPARENT);
droid.draw(canvas);
butt.draw(canvas);
butt1.draw(canvas);
butt2.draw(canvas);
butt3.draw(canvas);
buttz.draw(canvas);
buttz1.draw(canvas);
buttz2.draw(canvas);
buttz3.draw(canvas);
buttx.draw(canvas);
}
/**
* This is the game update method. It iterates through all the objects
* and calls their update method if they have one or calls specific
* engine's update method.
*/
public void update() {
droid.update();
}
public void handleActionDown(int eventX, int eventY) {
if (eventX >= (butt.x - butt.bitmap.getWidth() / 2) && (eventX <= (butt.x + butt.bitmap.getWidth()/2))) {
if (eventY >= (buttz.y - buttz.bitmap.getHeight() / 2) && (eventY <= (buttz3.y + buttz3.bitmap.getHeight() / 2))) {
// droid touched
droid.setTouched(true);
} else {
droid.setTouched(false);
}
} else {
droid.setTouched(false);
}
if (eventX >= (buttz1.x - buttz1.bitmap.getWidth() / 2) && (eventX <= (buttz.x + buttz.bitmap.getWidth()/2))) {
if (eventY >= (butt1.y - butt1.bitmap.getHeight() / 2) && (eventY <= (butt1.y + butt1.bitmap.getHeight() / 2))) {
// droid touched
droid.setTouched1(true);
} else {
droid.setTouched1(false);
}
}else {
droid.setTouched1(false);
}
if (eventX >= (butt2.x - butt2.bitmap.getWidth() / 2) && (eventX <= (butt2.x + butt2.bitmap.getWidth()/2))) {
if (eventY >= (buttz1.y - buttz1.bitmap.getHeight() / 2) && (eventY <= (buttz2.y + buttz2.bitmap.getHeight() / 2))) {
// droid touched
droid.setTouched2(true);
} else {
droid.setTouched2(false);
}
}else {
droid.setTouched2(false);
}
if (eventX >= (buttz2.x - buttz2.bitmap.getWidth() / 2) && (eventX <= (buttz3.x + buttz3.bitmap.getWidth()/2))) {
if (eventY >= (butt3.y - butt3.bitmap.getHeight() / 2) && (eventY <= (butt3.y + butt3.bitmap.getHeight() / 2))) {
// droid touched
droid.setTouched3(true);
} else {
droid.setTouched3(false);
}
}else {
droid.setTouched3(false);
}
if (droid.touched & !droid.touched1 & !droid.touched3) {
if (!moved) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.heror);
moved = true;
}
}else {
moved = false;
}if (droid.touched1 & !droid.touched & !droid.touched2){
if (!moved1) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herou);
moved1 = true;
}
}else {
moved1 = false;
} if (droid.touched2 & !droid.touched1 & !droid.touched3){
if (!moved2) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herol);
moved2 = true;
}
}else {
moved2 = false;
} if (droid.touched3 & !droid.touched2 & !droid.touched){
if (!moved7) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herod);
moved7 = true;
}
}else {
moved7 = false;
} if (droid.touched & droid.touched1){
if (!moved3) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herour);
moved3 = true;
}
}else {
moved3 = false;
} if (droid.touched1 & droid.touched2){
if (!moved4) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.heroul);
moved4 = true;
}
}else {
moved4 = false;
} if (droid.touched2 & droid.touched3){
if (!moved5) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herodl);
moved5 = true;
}
}else {
moved5 = false;
} if (droid.touched3 & droid.touched){
if (!moved6) {
droid.bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.herodr);
moved6 = true;
}
}else {
moved6 = false;
}
}
}
and the Droid.java
/**
*
*/
package com.workspace.pockethero.model;
import android.graphics.Bitmap;
import android.graphics.Canvas;
/**
* This is a test droid that is dragged, dropped, moved, smashed against
* the wall and done other terrible things with.
* Wait till it gets a weapon!
*
* #author impaler
*
*/
public class Droid {
public Bitmap bitmap; // the actual bitmap
public int x; // the X coordinate
public int y; // the Y coordinate
public boolean touched; // if droid is touched/picked up
public boolean touched1; // if droid is touched/picked up
public boolean touched2; // if droid is touched/picked up
public boolean touched3; // if droid is touched/picked up
public Droid(Bitmap bitmap, int x, int y) {
this.bitmap = bitmap;
this.x = x;
this.y = y;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public void draw(Canvas canvas) {
canvas.drawBitmap(bitmap, x - (bitmap.getWidth() / 2), y - (bitmap.getHeight() / 2), null);
}
/**
* Method which updates the droid's internal state every tick
*/
public void setTouched(boolean touched) {
this.touched = touched;
}
public boolean isTouched() {
return touched;
}
public void setTouched1(boolean touched) {
this.touched1 = touched;
}
public boolean isTouched1() {
return touched1;
}
public void setTouched2(boolean touched) {
this.touched2 = touched;
}
public boolean isTouched2() {
return touched2;
}
public void setTouched3(boolean touched) {
this.touched3 = touched;
}
public boolean isTouched3() {
return touched3;
}
public void update() {
if (touched & !touched1 & !touched3) {
x += 1;
}else if (touched1 & !touched & !touched2){
y -= 1;
}else if (touched2 & !touched1 & !touched3){
x -= 1;
}else if (touched3 & !touched2 & !touched){
y += 1;
}else if (touched & touched1){
x += 1;
y -= 1;
}else if (touched1 & touched2){
x -= 1;
y -= 1;
}else if (touched2 & touched3){
x -= 1;
y += 1;
}else if (touched3 & touched){
x += 1;
y += 1;
}
}
/**
* Handles the {#link MotionEvent.ACTION_DOWN} event. If the event happens on the
* bitmap surface then the touched state is set to <code>true</code> otherwise to <code>false</code>
* #param eventX - the event's X coordinate
* #param eventY - the event's Y coordinate
*/
}
there are also other classes created for each button, but i didnt pasted them here, because the are practicly the same as droid.java
Re-drawing the whole frame each loop is the correct way to draw/move sprites and perform anything with canvas.
The buffer is cleared each frame and you need to re-draw the background and all objects at their specified position.
In your code render() and update() will not be called more frequently than approximately every 16milliseconds~ (60 frames per second), so all you have to think about is drawing the onscreen scene.
I'm not sure what you mean by 'laggy', maybe you have a performance issue related to the size of your Bitmaps or phone performance, but I hope this is close to what you were looking for.

Categories

Resources