I created an Handler to show different images of dice while my runnable is running. Usually, when i press the "Roll" button my timer counts down from 3 and application shows to value of dice. But sometimes, images continue to change even though the timer expires.
I think that my runnable is still running in the background. So, Hhw i can stop my handler completely when timer expires ?
package com.basarballioz.dicerollerdroid;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.Random;
public class OneDiceActivity extends AppCompatActivity {
TextView diceStatus;
Handler handler;
Runnable runnable;
ImageView diceView;
Button rollButton;
CountDownTimer rollTimer = null;
int diceNumber;
TextView diceResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.one_dice_activity);
//MAKE APPLICATION FULLSCREEN
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
diceStatus = findViewById(R.id.diceStatus);
rollButton = findViewById(R.id.rollButton);
diceView = findViewById(R.id.diceView);
diceResult = findViewById(R.id.diceResult);
diceStatus.setText("Press Roll!");
//Enable this ONLY IF you want to use OnClickListener method instead of onclick button
/*diceView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//rollDice();
}
});*/
}
public void rollDice(View view) {
diceStatus.setText("Rolling...");
rollTimer = new CountDownTimer(3000, 1000) {
@Override
public void onTick(final long millisUntilFinished) {
rollButton.setEnabled(false);
handler = new Handler();
runnable = new Runnable() {
@Override
public void run() {
Random ranNumber = new Random();
diceNumber = ranNumber.nextInt(6) + 1;
switch (diceNumber) {
case 1:
diceView.setImageResource(R.drawable.dice1);
diceResult.setText("1");
break;
case 2:
diceView.setImageResource(R.drawable.dice2);
diceResult.setText("2");
break;
case 3:
diceView.setImageResource(R.drawable.dice3);
diceResult.setText("3");
break;
case 4:
diceView.setImageResource(R.drawable.dice4);
diceResult.setText("4");
break;
case 5:
diceView.setImageResource(R.drawable.dice5);
diceResult.setText("5");
break;
case 6:
diceView.setImageResource(R.drawable.dice6);
diceResult.setText("6");
break;
default:
break;
}
handler.postDelayed(runnable, 100);
}
};
handler.post(runnable);
}
@Override
public void onFinish() {
rollButton.setEnabled(true);
showDiceNumber();
handler.removeCallbacks(runnable);
}
}.start();
}
public void showDiceNumber() {
diceStatus.setText("Your dice is: ");
}
@Override
public void onBackPressed() {
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
Application's GitHub link: https://github.com/basarballioz/Dice-Rollerdroid
This line
new CountDownTimer(3000, 1000)
creates a countdown timer which invokes onTick
after 1 second elapsed, after 2 seconds elapsed and possibly after 3 seconds elapsed (not relevant but worth verifying).
On each of the invocations of onTick
(2 or 3 invocations) you create a new handler and assign it:
handler = new Handler();
and post
it. Everytime each handler completes (2 or 3 handlers now), they reschedule for 100 milliseconds later:
handler.postDelayed(runnable, 100);
Note the runnable instance is recreated as well which is not necessary.
When onFinish
is invoked (after 2 or 3 ticks) - there are 2 or 3 handlers running and repeating every 100 milliseconds.
Only the last handler or (2nd or 3rd) is canceled by the onFinish
and the initial 1 or 2 handlers continue to run.
Picture...
You definitely don't need more than 1 handler and runnable instance - in fact, you don't need a handler at all since CountDownTimer
is essentially doing this already. Whether you use a CountDownTimer
or just a plain Handler
with a counter is up to you.
If the desire is to have the dice face (value shown) change every 100 millis for a duration of 3 seconds after a 1 second delay, then change the onTick
to only start a handler once (or eliminate the handler altogether - see suggested code). (Note currently implementation starts changing dice after an initial 1 second.)
(Probably need to state in post what you really want to have happen.)
The referenced library is incorrectly implemented.
I'd suggest a simpler approach that eliminates the redundancy of the CountDownTimer code:
public void rollDice(View view) {
diceStatus.setText("Rolling...");
// Note the random is only created once.
final Random ranNumber = new Random();
rollButton.setEnabled(false);
// updates every 100 millis after an initial 100milli delay.
rollTimer = new CountDownTimer(3000, 100) {
@Override
public void onTick(final long millisUntilFinished) {
diceNumber = ranNumber.nextInt(6) + 1;
// use same switch statement as original post - sets the dice face.
}
@Override
public void onFinish() {
rollButton.setEnabled(true);
showDiceNumber();
}
}.start();
}
And if you really need an initial 1-second delay before rolling then don't immediately start()
it but add something like:
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
rollTimer.start();
}
}, 1000);