I am new in Android. I am currently developing a game and when I press the back button I realize that my activity's state is not saved so I search and I found out about Android Lifecycle. I read it and now i know how they work but the thing is I don't know how to pause and resume my activity or What code should I use because it is the auto-generated grid and my timer is CountDownTimer.
When I press the Back button this Activity is what appears.
GameModeActivity
Now i want to detect if the game is still going on .. so if I click again the game mode this is what should appear.
GamePlayActivity
The problem is The grid was back to its normal state. and when I click a button timer never runs. If possible i want to restore the player's progress (like the position of mines, the flags, and the timer itself.).
My question is ... Is there a specific code to get the whole state of activity? so that i can just pause it and when i came back it will just resume?
This is the code of timer and grid in GameEngine class:
public static GameEngine getInstance() {
if (instance == null) {
instance = new GameEngine();
}
return instance;
}
private GameEngine() {
}
public void startit() {
countdowntimer = new CountDownTimer(60000, 1000) {
TextView timer = (TextView) ((GamePlayActivity) context).findViewById(R.id.tvTimeValue);
#Override
public void onTick(long millisUntilFinished) {
timer.setText("" + millisUntilFinished / 1000);
}
#Override
public void onFinish() {
timer.setText("Times up!");
timer.setTextColor(Color.RED);
}
}.start();
}
public static void cancel() {
if (countdowntimer != null) {
countdowntimer.cancel();
countdowntimer = null;
}
}
public void createGrid(Context context) {
Log.e("GameEngine", "Creating Grid..");
this.context = context;
int[][] GeneratedGrid = Generator.generate(bombnumber, WIDTH, HEIGHT);
setGrid(context, GeneratedGrid);
}
private void setGrid(final Context context, final int[][] grid) {
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
if (MinesweeperGrid[x][y] == null) {
MinesweeperGrid[x][y] = new Cell(context, x, y);
}
MinesweeperGrid[x][y].setValue(grid[x][y]);
MinesweeperGrid[x][y].invalidate();
}
}
}
public Cell getCellposition(int position) {
int x = position % WIDTH;
int y = position / WIDTH;
return MinesweeperGrid[x][y];
}
public Cell getCellposition(int x, int y) {
return MinesweeperGrid[x][y];
}
I call the GameEngine.getInstance().createGrid(this) class in onCreate method of GamePlayActivity
And when I press the Back button on phone it will go to the previous activity which is GameModeActivity.
Related
I have a problem with a simple android application. It has a SurfaceView with simple drawing, but activity orientation sometimes seems not to be changed after a screen rotation.
This is how an activity looks in the portrait mode:
landscape mode:
but sometimes, when I rotate the screen, an activity looks like this in the portrait mode:
MainActivity.java:
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback
{
private Thread mGameThread;
private GameApp mGameApp;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SurfaceView mainView = (SurfaceView)findViewById(R.id.mainView);
SurfaceHolder holder = mainView.getHolder();
holder.addCallback(this);
mGameApp = new GameApp(getResources(), holder);
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
mGameThread = new Thread(new Runnable()
{
#Override
public void run()
{
mGameApp.run();
}
});
mGameThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
mGameApp.setSurfaceSize(width, height);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
mGameApp.setRunning(false);
while (retry)
{
try
{
mGameThread.join();
retry = false;
}
catch (InterruptedException e)
{
}
}
}
}
GameApp.java:
package com.example.myapplication;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.view.SurfaceHolder;
public class GameApp
{
private Resources mResources;
private SurfaceHolder mSurfaceHolder;
private int mCanvasHeight = 1;
private int mCanvasWidth = 1;
private volatile boolean mRun = false;
public GameApp(Resources resources, SurfaceHolder surfaceHolder)
{
mResources = resources;
mSurfaceHolder = surfaceHolder;
}
public void setSurfaceSize(int width, int height)
{
synchronized (mSurfaceHolder)
{
mCanvasWidth = width;
mCanvasHeight = height;
}
}
public void run()
{
setRunning(true);
while (mRun)
{
Canvas canvas = null;
try
{
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
if (mRun && canvas != null)
{
draw(canvas);
}
}
}
finally
{
if (canvas != null)
{
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
public void setRunning(boolean b)
{
mRun = b;
}
private void draw(Canvas canvas)
{
canvas.drawColor(Color.GREEN);
Drawable cellImage = mResources.getDrawable(R.drawable.cell);
final float cellWidth = mCanvasWidth / 6;
final float cellHeight = mCanvasHeight / 6;
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 6; j++)
{
float x = i * cellWidth;
float y = j * cellHeight;
cellImage.setBounds(Math.round(x), Math.round(y), Math.round(x + cellWidth), Math.round(y + cellHeight));
cellImage.draw(canvas);
}
}
}
}
Eventual answer:
The problem is that you are doing a while loop in your GameApp's thread that locks the canvas and then unlocks without any long blocking or sleep in between.
The SurfaceHolder#lockCanvas documentation states:
If null is not returned, this function internally holds a lock until the corresponding unlockCanvasAndPost(Canvas) call, preventing SurfaceView from creating, destroying, or modifying the surface while it is being drawn.
So this means that the destroy code which is run from main thread needs to run between the unlockCanvasAndPost and the next lockCanvas. But since you have no sleep or even other code in between (except for the while condintion check), the chance of this happening is very small, and - depending on the device - could practically take forever.
To fix this, put a sleep in your game app to achieve the wanted framerate,
in it's most simple from this could look like this.
class GameApp
...
while (mRun)
{
Canvas canvas = null;
try
{
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
if (mRun && canvas != null)
{
draw(canvas);
}
}
}
finally
{
if (canvas != null)
{
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
// Add some sleep time depending on how fast you want to refresh
try {
Thread.sleep(1000/60); //~60 fps
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Original answer 1: What could have helped in case of handling orientation changes
Seems like the surface is not always re-drawn on a config change.
Try overriding the onConfigurationChanged method of the activity and then triggering a re-layout of the surface
in MainActivity.java:
...
#Override
public void onConfigurationChanged(Configuration newConfig) {
// You should save (SurfaceView)findViewById(R.id.mainView) in a field for better performance, but I'm putting it here for shorter code.
SurfaceView mainView = (SurfaceView)findViewById(R.id.mainView);
mainView.invalidate();
mainView.requestLayout();
}
...
More info on these methods nicely explained here:
Usage of forceLayout(), requestLayout() and invalidate()
Original answer 2: A way to check if your threads are blocked
Another possibility is that you have a thread lock issue on your main thread.
For checking that you could change your activity like this:
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private Thread mGameThread;
private GameApp mGameApp;
private static View currMainView;
private static Thread logPosterThread = new Thread(new Runnable() {
volatile boolean mainLogDone = true;
#Override
public void run() {
Runnable mainThreadLogger = new Runnable() {
#Override
public void run() {
Log.d("THREAD_CHECK", "Main Thread is ok");
mainLogDone = true;
}
};
while (true) {
try {
int sleepTime = 1000;
Thread.sleep(sleepTime);
if (currMainView != null) {
if (mainLogDone) {
mainLogDone = false;
Log.d("THREAD_CHECK", "Main Thread doing post");
currMainView.post(mainThreadLogger);
} else {
Log.w("THREAD_CHECK", "Main Thread did not run the runnable within " + sleepTime + "ms");
mainLogDone = true;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
static {
logPosterThread.start();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SurfaceView mainView = (SurfaceView) findViewById(R.id.mainView);
currMainView = mainView;
SurfaceHolder holder = mainView.getHolder();
holder.addCallback(this);
mGameApp = new GameApp(getResources(), holder);
}
#Override
public void onPause() {
...
And then see if those logs still get printed with 1000ms in between when the issue happens.
When you try this, you will see that actually the main thread is hanging when this happens.
I have this swapAnimation method which basically swaps two views. I call this method inside a loop and pass different views on the swapAnimation method each time. But the problem is the animation happens only once. I want it to repeat n number of times.
void swapAnimation(View v1,View v2){
if(isAnimating)return;
isAnimating = true;
float x1,y1,x2,y2;
x1 =getRelativeX(v1);
x2 = getRelativeX(v2);
y1 = getRelativeY(v1);
y2 = getRelativeY(v2);
float x_displacement = (x2-x1);
float y_displacement = (y2-y1);
v1.animate().xBy(x_displacement).yBy(y_displacement);
v2.animate().xBy(-x_displacement).yBy(-y_displacement);
v1.animate().setDuration(500);
v2.animate().setDuration(500);
long duration = v1.animate().getDuration();
new CountDownTimer(duration+10,duration+10){
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
isAnimating = false;
}
}.start();
}
public static void arrange(LinearLayout container, Context context){
MainActivity activity = (MainActivity) context;
for(int i=0;i<container.getChildCount();i++){
BarView v1 = (BarView) container.getChildAt(i);
for(int j=i;j<container.getChildCount();j++){
BarView v2 = (BarView) container.getChildAt(j);
if(v1.getWeight() > v2.getWeight()){
Log.d(TAG, "bubbleSort: "+v1.getWeight()+">"+v2.getWeight());
activity.swapAnimation(v1,v2);
}
}
}
}
Try this
animation.setRepeatCount(Animation.INFINITE);
I am practising on a simple Android Game where a round button is randomly placed on the screen when the user taps on it..
it works fine but i want to speedify the process of placing the button so that the game gets harder for user...
here is the Code I'm using -
public class GameWindow extends Activity implements View.OnClickListener {
static int score;
private Timer t;
private int TimeCounter = 29;
private boolean canMove = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
////Remove title screen for activty.
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_game_window);
moveButton();
endonTimeOver();
}
public void endonTimeOver(){
////Activity timer for 60 seconds.
final TextView timer = (TextView) findViewById(R.id.seconds);
t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
////Set string to timer.
#Override
public void run() {
// TODO Auto-generated method stub
runOnUiThread(new Runnable() {
public void run() {
timer.setText(String.valueOf(TimeCounter)); // you can set it to a textView to show it to the user to see the time passing while he is writing.
TimeCounter = TimeCounter - 1;
}
});
}
}, 1000, 1000); // 1000 means start from 1 sec, and the second 1000 is do the loop each 1 sec.
new Timer().schedule(new TimerTask(){
public void run() {
GameWindow.this.runOnUiThread(new Runnable() {
public void run() {
startActivity(new Intent(GameWindow.this, Finished.class));
}
});
}
}, 30000);
}
////Move button.
private void moveButton()
{
if(!canMove){ return; }
runOnUiThread(
new Runnable()
{
#Override
public void run()
{
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
Button button = (Button)findViewById(R.id.button);
Random r = new Random();
int startX = width/2;
int startY = height/2;
if(score==0){
button.setX(startX);
button.setY(startY);
}
else {
int x = r.nextInt(width - 210);
int y = r.nextInt(height - 200);
button.setX(x);
button.setY(y);
}
}
}
);
}
////Display score
public void displayScore(int score) {
TextView scoreView = (TextView) findViewById(R.id.score);
scoreView.setText(String.valueOf(score));
}
#Override
public void onClick(View v) {
MediaPlayer mp = MediaPlayer.create(this, R.raw.buttonsound);
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
// TODO Auto-generated method stub
mp.release();
}
});
mp.start();
score = score + 1;
displayScore(score);
switch (v.getId()) {
case (R.id.button): {
moveButton();
}
}
}
public static int getScore(){
return score;
}}
Use global variables for values that don't change:
findViewById is slow
Creating new Random every time is not necessary
getting the window parameter every time is not necessary either
You seem to be starting a new thread and telling it to runonui , this might be slowing you down , try this :
private void moveButton()
{
if(!canMove){ return; }
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
Button button = (Button)findViewById(R.id.button);
Random r = new Random();
int startX = width/2;
int startY = height/2;
if(score==0){
button.setX(startX);
button.setY(startY);
}
else {
int x = r.nextInt(width - 210);
int y = r.nextInt(height - 200);
button.setX(x);
button.setY(y);
}
}
the computation itself doesn't seem to heavy so no need for another thread , you can do it on the main thread , if you're calling from oncreate that means you're already on main thread , this might give you the answer if i understood the question , try it
i cannot move a custom view with this code :
Ball.java
public class Ball extends View {
int x, y;
public Ball(Context context) {
super(context);
}
public void setSizes( int x, int y) {
this.x = x;
this.y = y;
}
protected void onDraw(Canvas c) {
Paint paint = new Paint();
paint.setColor(Color.BLACK);
int radius = 50;
c.drawCircle(x,y,radius,paint);
}
}
BounceLoop.java
public class BounceLoop extends Thread {
int width, height, x, y;
boolean jumping = false;
public void setSizes(int width, int height) {
this.width = width;
this.height = height;
}
public void run() {
jumping = true;
x = 0;
y = 0;
while(jumping) {
}
}
}
and MyActivity.java
public class MyActivity extends Activity {
RelativeLayout content;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
content = (RelativeLayout) findViewById(R.id.content);
BounceLoop thread = new BounceLoop();
thread.setSizes(content.getWidth(), content.getHeight());
thread.start();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.my, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
How do i move the ball from the BounceLoop thread ? I don't want to do it with AsyncTask, do i have to use handlers or ?
your thread will probably need a reference of your custom view and in the run() I would have expected something like
public void run() {
jumping = true;
x = 0;
y = 0;
while(jumping) {
mCircleView.setPosition(newX, newY);
mCircleView.postInvalidate();
}
}
be aware that you can't touch the UI Through another thread, this is way I called postInvalidate() instead of invalidate(). The former will reschedule a draw event on the UI Thread
Finally i did this :
public class MyActivity extends Activity {
RelativeLayout content;
Handler myHandler;
Ball view;
Thread loop;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
content = (RelativeLayout) findViewById(R.id.content);
view = new Ball(this);
view.setPosition(50, 50);
content.addView(view);
myHandler = new Handler() {
public void handleMessage(Message msg) {
int x = (int) msg.arg1;
int y = (int) msg.arg2;
Log.d("ss", String.valueOf(x));
view.setPosition(x, y);
}
};
}
public void onWindowFocusChanged(boolean hasChanged) {
super.onWindowFocusChanged(hasChanged);
loop = new Thread(new Runnable() {
#Override
public void run() {
int x = 0;
int y = 0;
Log.d("width", String.valueOf(view.getWidth()));
while (x < content.getWidth() - view.radius) {
x += 5;
y += 7;
Message msg = new Message();
msg.arg1 = x;
msg.arg2 = y;
myHandler.sendMessage(msg);
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
loop.start();
}
}
but this is not comfortable for me to have the thread in the activity, how can i export it to another class...
I have a game where image Views changes on a timer. When a button is clicked (stop) the update is called to update score and spins. I need to stop when the whammy(); is called and run a frame animation then start the timer back when the animation has ended. I have tried to call sleep(2500); but it cause the everything to wait the 2.5 seconds and the frame animation then runs with the image view timer. That just won't work for what I need. Below is the section of code I need help with:
public void onClick(View v) {
if (v.getId() == R.id.btstop) {
final Button bstop = (Button) findViewById(R.id.btstop);
bstop.setEnabled(false);
updateState();
}
}
/**
* set random view. get random 1 point from class utils and display to 1
* position random
*/
public void setRandomView() {
int item = new Random().nextInt(18);
current = Utils.getAPoint();
int img = current.getImg();
int score = current.getScore();
int spin = current.getSpin();
iv[item].setImageResource(img);
dataPoint.get(item).setImg(img);
dataPoint.get(item).setScore(score);
dataPoint.get(item).setSpin(spin);
ivcenter.setImageResource(img);
}
/**
* display Name,score & spin to screen
*/
public void setPoint() {
tvspin.setText(spins + "\n" + name);
tvpoint.setText(point + "");
}
/**
* Start Thread Sets the speed of the game board Enable STOP button to true
*
* #param time
*/
public void start(int time) {
countDownTimer = new CountDownTimer(time, 300) {
#Override
public void onTick(long millisUntilFinished) {
// call to set random view
setRandomView();
}
#Override
public void onFinish() {
// TODO Auto-generated method stub
}
}.start();
}
/**
* stop timer thread
*/
public void stop() {
try {
countDownTimer.cancel();
} catch (Exception e) {
// TODO: handle exception
} finally {
}
}
/**
* update state game and check what tile is in the center
*/
public void updateState() { // get score,spin current point
int scoreCurrent = current.getScore();
int spinCurrent = current.getSpin();
spins--;// Take one from spins
// if score = 2, that mean to double score
if (scoreCurrent == 2) {
point = point * 2;
playmp3(R.raw.correct);
}
// if score = 0, that means tile is a whammy
if (scoreCurrent == 0) {
playmp3(R.raw.buzz);
whammy();// I need to put everything else on hold here and animation here
point = 0;// sets the score to 0
whammy++; // Add a whammy to the whammy count
// Display the whammy that was added
for (int i = 0; i < whammy; i++) {
popup[i].setVisibility(View.VISIBLE);
}
// sleep(2500);
if (whammy == 4) {
stop();
stopmp3();
callFinish();
}
}
// else ,score = score current + point.getscore
else {
playmp3(R.raw.correct);
point = point + scoreCurrent;
}
// spins--;// Take one from spint
// if spin > 0, that mean is add a spin
if (spinCurrent > 0) {
spins = spins + spinCurrent;
}
setPoint(); // display spin,score and name to screen
sleep(2000); // Adds a delay so that the whammy pop up can be seen
// if spint >0 start new data random
if (spins > 0) {
stop();
final Button bstop = (Button) findViewById(R.id.btstop);
bstop.setEnabled(true);
start(10000 * 60 * 60 * 24);
} else {
// stop all
stop();
stopmp3();
// if spin < 2, that round <2 move to spin activity
if (round < 2) {
Intent it = new Intent(getBaseContext(), SpinActivity.class);
startActivity(it);
finish();
}
// else move to high score activity
else {
callFinish();
}
}
}
private void whammy() {
// set up the whammy act animation
final ImageView act = (ImageView) findViewById(R.id.whammyact);
act.setBackgroundResource(R.drawable.tnt_animation);
actAnim = (AnimationDrawable) act.getBackground();
actAnim.setVisible(true, true);
}
OK Here are the changes that were made to make the animation run and finish before the game timer gets started back...
IF THERE ARE QUESTION OR ANYONE NEED HELP FEEL FREE TO ASK.
if (scoreCurrent == 0) {
countDownTimer.cancel();
countDownTimer = null;
// Use Timer to set visibility to GONE after the animation finishes.
TimerTask timerTask = new TimerTask(){
#Override
public void run() {
PressActivity.this.runOnUiThread(new Runnable(){
public void run() {
resumeLogicAfterPress();
}
});}};
Timer timer = new Timer();
timer.schedule(timerTask, getWhammyAnimationTotalDuration(actAnim));
} else {
resumeLogicAfterPress();
}
}
public void resumeLogicAfterPress() {
// Display the whammy that was added
for (int i = 0; i < whammy; i++) {
popup[i].setVisibility(View.VISIBLE);
}
sleep(2500); //Adds a delay so that the whammy pop up can be seen
//Start the background music
playmp3(mpPress, R.raw.press);
// if whammy = 4. stop all and move to high score
if (whammy == 4) {
stop();
stopmp3();
callFinish();
}
// if spint >0 start new data random
if (spint > 0) {
stop();
final Button bstop = (Button) findViewById(R.id.btstop);
bstop.setEnabled(true);
start(10000 * 60 * 60 * 24);
} else {
// stop all
stop();
stopmp3();
// if spin < 2, that round <2 move to spin activity
if (spin < 2) {
Intent it = new Intent(getBaseContext(), SpinActivity.class);
startActivity(it);
finish();
}
// esle move to high score activity
else {
callFinish();
}
}
}
private void whammy() {
// set up the whammy act animation
final ImageView act = (ImageView) findViewById(R.id.whammyact);
act.setBackgroundResource(R.drawable.tnt_animation);
actAnim = (AnimationDrawable) act.getBackground();
actAnim.setVisible(true, true);
}