I developed an standalone app for a smartwatch, and I want to have an OnTouchListner which detect if a Touch lasted for at least 3 sec and perform action only in that case.
Here is the code I use : (a simple boolean state which is set to false when MotionEvent is up and a PostDelayed action on the Handler)
private int longClickDuration = 3000;
private boolean isLongPress = false;
static Runnable longTouchRunnable;
mContainerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
longTouchRunnable = new Runnable() {
#Override
public void run() {
if (isLongPress) {
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
long[] vibrationPattern = {0, 500, 50, 300};
//-1 - don't repeat
final int indexInPatternToRepeat = -1;
vibrator.vibrate(vibrationPattern, indexInPatternToRepeat);
// set your code here
setStateStarting(); //Block the GUI, only one click and waiting for tablet
presenter.onStartClick();
Log.d("Long","LONG CLICK PERFORMED");
// Don't forgot to add <uses-permission android:name="android.permission.VIBRATE" /> to vibrate.
}
}
};
switch (currentState) {
case START: {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
isLongPress = true;
Log.e("Long touch","Start");
myUIHandler.postDelayed(longTouchRunnable, longClickDuration);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
isLongPress = false;
myUIHandler.removeCallbacks(longTouchRunnable);
Log.e("Long touch", "Stop");
}
return true;
}
}
return false;
}
});
My problem :
Everything work perfectly expect that the Runnable doesn't seem to be delete when it's necessary.
--> If I click quickly two times on the view and then a third time and holding it (So the boolean state is true for the three PostDelayed which came after 3 sec) I will have my specific action code executed 3 times --> the Runnable task wasn't removed with the ACTION_UP event.
Info : The message debug are prompt as expected...
How can I properly destroy a Runnable that was postdelayed in a handler ?
Thanks
handler.removeCallbacksAndMessages(null);
In the docs for removeCallbacksAndMessages it says...
Remove any pending posts of callbacks and sent messages whose obj is token. If a token is null, all callbacks and messages will be removed.
removeCallbacks only stops pending messages (Runnables). If your runnable has already started, then there's no stopping it (at least not this way).
Alternatively, you can extend the Runnable class and give it some kind of kill switch like this:
public class MyRunnable implements Runnable
{
public boolean killMe = false;
public void run()
{
if(killMe)
return;
/* do your work */
}
public void killRunnable()
{
killMe = true;
}
}
This will only prevent it from starting, but you could occasionally check killMe and bail out. If you are looping the runnable (like some kind of background thread) you can say:
while(!killMe) {
/* do work */
}
Related
I've been working on a sort of a game engine for Android but I have some serious stability issues.
I use a SurfaceView with an implemented Runnable for Multithreading, to get some work of my main / UI thread. The simplified version of this class is shown below:
private class MenuView extends SurfaceView implements Runnable
{
Thread T = null;
SurfaceHolder holder;
Canvas c;
boolean run;
public MenuView(Context context) {
super(context);
holder = getHolder();
}
#Override
public void run() {
while(run)
{
if(!holder.getSurface().isValid())
{
continue;
}
c = holder.lockCanvas();
//Doing updates and drawing stuff
holder.unlockCanvasAndPost(c);
}
}
public void pause()
{
run = false;
try
{
T.join();
}
catch (InterruptedException e)
{
Log.e("Error:", "joining thread failed!");
}
}
public void resume()
{
run = true;
T = new Thread(this);
T.start();
}
//bunch of helper methods with no effect on this problem :(
}
So as you can see, my "Worker" thread is caught in an infinite while loop that is controlled by the "run" boolean, and this boolean is set using pause() and resume() methods called in onPause() and onResume() methods of my activity as shown here:
MenuView menuV;
Random Rand = new Random();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Screen = getRealScreenSize(getApplicationContext());
menuV = new MenuView(this);
menuV.setOnTouchListener(this);
logoV = new LogoView(this);
setContentView(logoV); Log.d("APP", "SET LOGO VIEW");
//^ This is my splash screen, again irrelevant to the issue, after few seconds of running, this view calls the resume() of my SurfaceView and sets it as the contentView.
}
#Override
protected void onPause() {
super.onPause();
Log.d("APP", "ON PAUSE");
menuV.pause();
}
#Override
protected void onResume() {
super.onResume();
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
//^hides the goddamn navigation bar...
Log.d("APP", "ON RESUME");
menuV.resume();
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
Log.d("APP", "ON TOUCH DOWN");
for(int i = 0; i < menuV.button.length ; i++)
{
if(menuV.button[i].active)
{
if(event.getX() > menuV.button[i].pos.left && event.getX() < menuV.button[i].pos.right && event.getY() > menuV.button[i].pos.top && event.getY() < menuV.button[i].pos.bottom)
{
menuV.button[i].pressed = true;
return true;
}
}
}
}
else if(event.getAction() == MotionEvent.ACTION_UP)
{
Log.d("APP", "ON TOUCH UP");
for(int i = 0; i < menuV.button.length ; i++)
{
menuV.button[i].pressed = false;
}
}
return false;
}
So my touch event is handled by the UI thread, but it's accesing info that the worker thread is constantly using, the same thing happens in onPause() and onResume(), the UI thread is changing the "run" boolean which is always used by the worker thread, I'm saying this because it seems that the ANRs are happening when touching the screen and when power-off-ing and power-on-ing the phone. Rendering doesn't stop, it seems that the worker thread is just fine but the UI thread messes up badly, it stops responding and this line shows on my Android Monitor:
Thread[2,tid=18257,WaitingInMainSignalCatcherLoop,Thread*=0x558cbf1310,peer=0x12c470a0,"Signal Catcher"]: reacting to signal 3
Wrote stack traces to '/data/anr/traces.txt'
Please let me know if there is any further useful information - I will EDIT in anything you point out as important after this line, Thanks!
Try marking run variable as volatile:
volatile boolean run;
Maybe you have caching-related problem.
I currently have a while loop that starts when a user taps the screen. The loop is meant to be infinite, and should stop when it senses another screen tap from the user. (And, if tapped again, it'll start looping again, etc.) I’ve tried to break the loop in two different ways, with both yielding non-satisfactory results:
METHOD 1: When I try this method, what happens is that the user taps, the loop starts. The user taps again, and that tap doesn't get registered and the loop just keeps on going.
boolean playing = false;
#Override
public boolean onTouch(View v, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
if(!playing)
{
playing = true;
while(playing)
{
//do something here
}
}
else if(playing)
{
playing = false;
}
}
return false;
}
METHOD 2: When I try this method, what happens is that the user taps, the loop starts, goes through one iteration, then stops.
boolean playing = false;
#Override
public boolean onTouch(View v, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
if(!playing)
{
playing = true;
whileLoop:
while(playing)
{
//do something here
}
}
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
playing = false;
break whileLoop;
}
}
return false;
}
I've looked at these questions, but neither helped me with my specific situation:
How do I exit a continuous while loop by some user input?
How to exit an infinite while loop using user input?
So how do I implement this while loop? I just want a simple interface where a user's tap would toggle the while loop on/off.
Put the infinite loop within a Thread. Having it there, you can just have a control variable inside (like playing), and once tap again and you want to stop, simply set it to false. The Thread, running paralelly, will be able to handle both events (the tap and the Thread stop).
---- EDIT ----
Example added:
// You'd probably need to store this variable class-wide,
// so you can control when the user has tapped for the first or second time
boolean playing = false;
#Override
public boolean onTouch(View v, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
if(!playing)
{
playing = true;
new Thread(
new Runnable() {
public void run() {
while(playing)
{
//do something here
}
}
}
).start();
}
else if(playing)
{
playing = false;
// As you're controlling the playing value in the Thread,
// setting it here to false would mean that your thread stops too.
}
}
return false;
}
try this:
boolean playing = false;
Handler handler = new Handler();
Runnable runner = new Runnable() {
#Override
public void run() {
// do something here
handler.post(this);
}
};
imageView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
if(!playing) {
playing = true;
runner.run();
return true;
}
if(playing) {
playing = false;
handler.removeCallbacks(runner);
handler = null;
runner = null;
Toast.makeText(context, "NO", Toast.LENGTH_SHORT).show();
return true;
}
}
return false;
}
});
I want to check that view (no matter which, in my case ImageView) is not touched/clicked by user, and I want to do it constantly. I think that i should use some kind of thread but I have no idea how to start. The operation which I want to do is hiding the ActionBar, when there is no action I want to hide it.
When view is touched I use this code:
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
//if (registerImageViewCallback(slidesPagerAdapter);
gestureImageView = slidesPagerAdapter.getGestureImageView();
gestureImageView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
getSupportActionBar().show();
return false;
}
});
}
}, 10);
But what I have to do when screen is not touched? :-)
Create an additional boolean (ex: notTouched) to true, then set it to false inside your onTouch override, then at any point you can just check if the boolean is true or false.
EDIT :
If you want it to hide it after it has not been touched for a while (as you said)
you could implement an additional counter integer in a loop on a separate thread like this:
while(true) {
if (notTouched) {
counter++
if(counter == 20) { // Assuming 20 seconds have passed of not touching
hideMethodHere(); // Execute hiding
}
Thread.sleep(1000); // Sleep for a second (so we dont have to count in miliseconds)
} else {
counter = 0; // If it was touched, reset the counter
notTouched == true; // Reset the touch flag as well because otherwise it will be visible forever if you touch it
}
}
Add a flag to your Activity:
boolean wasTouched = true;
Add a timer that checks if the view was touched:
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
if ( wasTouched == false ) {
runOnUiThread(new Runnable() {
public void run(){
hideYourActionBar();
}
});
} else {
wasTouched = false;
}
}
}, 0, YourDelayInMsHere);
In your onTouchListener, set wasTouched to true:
public boolean onTouch(View v, MotionEvent event) {
wasTouched = true;
}
This should at least give you an idea of how to do it.
I have a few issues I am having a hard time find good information on how to fix. First off I have a Game Over screen that shows up when it should. I have tried to use setting the Threads running to false so it stop running and then when the screen is touched to set it back to true, but it does not take the screen back to it running. Also I'm going to need to be able to actually clear all the times so that it resets it (what is a good way to do that). This similarly I will need I am assuming for the Pause and Resume. If running isnt set to false it appears to continue running even with sleep() just in slower increments. Here is my thread to see if you notice what I can do.
public class GameLoopThread extends Thread {
private GameView view;
public static boolean running = false;
static final long FPS = 10;
public GameLoopThread(GameView view) {
this.view = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running) {
Canvas c = null;
startTime = System.currentTimeMillis();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS-(System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
} catch (Exception e) {}
}
}
}
Now this part is causing a large issue for the Pause/Resume. I have a handler.postDelayed handling the spawning of my sprites. Basically when the screen appears paused (cause it looks like it is if i use the sleep) the problem is that the handler (which is in the GameView) is seeming like the time is still running for it. Is there a way to reset it back to zero... preferably on both the classes for the Game Over and to pause the handler as well during the Pause? Thanks
EDIT: As of right now I'm trying to use an options menu to make a new game by making it reopen the activity that runs the game. It appears to reset it but it freezes it in the process. Anyone know how I can fix this?
#Override
public boolean onCreateOptionsMenu(Menu menu){
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.newgame:
GameView.gameState=0;
GameLoopThread.running=true;
Intent game = new Intent(PlayGame.this, PlayGame.class);
startActivity(game);
break;
case R.id.pause: Toast.makeText(this, "You pressed Pause", Toast.LENGTH_LONG).show();
break;
case R.id.quit: Toast.makeText(this, "You pressed Quit", Toast.LENGTH_LONG).show();
break;
}
return true;
}
Thats my options menu.
This may not seem like the anwser you are looking for, but if you want to create code that's easily extendable (and learn a new desgin pattern!) then this is the way to go.
Most games use a State Machine for these kind of things.
For example
interface GameState{
public void render(long elapsedTime);
}
Now you can create different states, like a MenuState, GameState, PauseState, OptionState, GameOverState, etc.
Some pseudo code to get the idea across:
State currentState = null;
public void run(){
while(true){
// Do your loop here
currentState.render(time);
}
}
public void setState(State state){
currentState = state;
}
States Example:
public class MenuState implements GameState{
public void render(long elapsedTime){
//Render menu here
}
}
Or
public class GameState implements GameState{
public void render(long elapsedTime){
//Render gameplay here
}
}
currentState points to the current state and decides which one gets rendered/updated. Just change it's reference and your pause menu gets drawn.
This is a very common design pattern in games, search around!
Good luck!
Well I have made a custom Android UI, and I need my UI view to handle this control.
a) While my mouse button is pressed (onDown / Key pressed) , the view should keep doing a thing (For example : Key is down)
b) As soon as i release my mouse key , the view should say (example: Key is up).
The Sample Flow should be something like when I press the view.
Output:
(I press mouse button on the view and hold it)
Key is down
Key is down
Key is down
Key is down
Key is down
Key is down
(I now release mouse button)
Key is up.
To more explain my problem . I am posting a code snippet where the logic should go
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, event.getAction());
}
When I press my mouse button , it prints "0" (meaning mouse is down), while if i leave it , it prints on the log "1", meaning mouse is up. That should help with the logic.
Thanks for helping.
Well I have tried something like
private static int checkValue = 0;
#Override
public boolean onTouchEvent(MotionEvent event) {
checkValue = event.getAction();
while (checkValue == 0 ){
Log.d(TAG, "Pressed");
checkValue = checkMethod(event.getAction);
}
private int checkMethod(int test){
if (checkValue == 0){
checkValue = 0;
}
else checkValue = 1;
}
Just define an OnTouchEventListener...
private OnTouchListener onTouchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.button) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//start loop or background task
} else if (event.getAction() == MotionEvent.ACTION_UP) {
//do something different
}
}
return true;
}
}
...and assign it to your button.
button.setOnTouchListener(onTouchListener);
When the user touches the button the ACTION_DOWN event is fired. When the user releases the button, the ACTION_UP event is fired. If you want you can start a loop in a thread which can check against a boolean variable. I didn't check if it is correct but it should look like this:
private OnTouchListener onTouchListener = new OnTouchListener() {
private boolean flag = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.button) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
flag = true;
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while(flag) {
//do something
}
}
});
thread.start();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
flag = false;
}
}
return true;
}
}
Maybe you could create and start a thread when the touch occurs that has a volatile boolean stop = false field and that checks for this value in every loop after sleeping for a while. When the touch ends, you can set the boolean variable to false.
Edit: added an example
private MyHandler h;
public void handleDown() {
System.out.println("touch began");
h = new MyHandler();
h.start();
}
public void handleUp() {
h.pleaseStop = true;
System.out.println("touch ended");
}
class MyHandler extends Thread {
volatile boolean pleaseStop = false;
public void run() {
while (!pleaseStop) {
System.out.println("touch is active...");
Thread.sleep(500);
}
}
}
checkValue = event.getAction();
while (checkValue == 0 ){
You should never ever be using magical integers. Use the constants as defined by the API:
http://developer.android.com/reference/android/view/MotionEvent.html#ACTION_DOWN
In fact your code isn't even accounting for move events; it seems to assume that if it isn't a down event it is an up... which is completely wrong.
Look at the documentation to see what action values you can expect, and do the right thing for the specific actions you are interested in. For example up:
http://developer.android.com/reference/android/view/MotionEvent.html#ACTION_UP
Create a thread which is initiated by the touch and have boolean flag to keep it alive. When the touch is released, set the flag to false so the thread joins (terminates).
Because the thread is independent from the main logic, the process is faster.