Continuously increase integer value as the button is pressed - android
I'm new to Android so sorry if the question is easy to answer. I have two buttons, a decrease and an increase button, and in the middle of them a TextView which displays a value.
When I hit the decrease button, the value in the TextView decreases and increases when I hit the increase button, no problem with that, I got that working but the problem is the value only increases/decreases by 1 on a single click. What I'm trying to achieve is that as I continuously press the button (the increase button for example), the value is also continuously increasing and only stops when I release the increase button.
Is that possible? If so, can you show some sample code or references on how to implement that? Thanks!
Here is my main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center" >
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="44dp"
android:gravity="center_horizontal" >
<Button android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="<" />
<TextView android:id="#+id/textView1"
android:layout_width="50dp"
android:layout_height="fill_parent"
android:layout_alignBottom="#+id/button1"
android:layout_toRightOf="#+id/button1"
android:gravity="center"
android:text="45" />
<Button android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toRightOf="#+id/textView1"
android:text=">" />
</RelativeLayout>
</RelativeLayout>
and here is my Main.java
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class Main extends Activity {
private Button _decrease;
private Button _increase;
private TextView _value;
private static int _counter = 45;
private String _stringVal;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
_decrease = (Button) findViewById(R.id.button1);
_increase = (Button) findViewById(R.id.button2);
_value = (TextView) findViewById(R.id.textView1);
_decrease.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("src", "Decreasing value...");
_counter--;
_stringVal = Integer.toString(_counter);
_value.setText(_stringVal);
}
});
_increase.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("src", "Increasing value...");
_counter++;
_stringVal = Integer.toString(_counter);
_value.setText(_stringVal);
}
});
}
}
For that to work, you need a thread that will update the integer value when you long press on a button.
Create a handler in your activity:
private Handler repeatUpdateHandler = new Handler();
And 2 vars which will state: is it increment or decrement? Only one set at a time.
private boolean mAutoIncrement = false;
private boolean mAutoDecrement = false;
And the present number value
public int mValue;
And a class that will run in another thread:
class RptUpdater implements Runnable {
public void run() {
if( mAutoIncrement ){
increment();
repeatUpdateHandler.postDelayed( new RptUpdater(), REP_DELAY );
} else if( mAutoDecrement ){
decrement();
repeatUpdateHandler.postDelayed( new RptUpdater(), REP_DELAY );
}
}
}
Add a long press listener to your button:
mBTIncrement.setOnLongClickListener(
new View.OnLongClickListener(){
public boolean onLongClick(View arg0) {
mAutoIncrement = true;
repeatUpdateHandler.post( new RptUpdater() );
return false;
}
}
);
mBTIncrement.setOnTouchListener( new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if( (event.getAction()==MotionEvent.ACTION_UP || event.getAction()==MotionEvent.ACTION_CANCEL)
&& mAutoIncrement ){
mAutoIncrement = false;
}
return false;
}
});
In the above case the button is the increment one. Create another button which will set mAutoDecrement to true.
And decrement() will be a function, which will set your instance int variable like this:
public void decrement(){
mValue--;
_value.setText( ""+mValue );
}
You figure the increment out. Oh and REP_DELAY is a static int variable set to 50.
I see this is an excerpt from Jeffrey Cole's open source NumberPicker available at http://www.technologichron.net/ Proper author's attribution must be added.
While the accepted answer is totally correct, it can be simplified a bit.
Basically, we can optimize two things:
We don't need the OnTouchListener.
We can instantiate the runnable object just once instead of creating multiple objects.
So this is my version:
// global variables
Handler handler = new Handler();
Runnable runnable;
increaseView.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
runnable = new Runnable() {
#Override
public void run() {
if (!increaseView.isPressed()) return;
increaseValue();
handler.postDelayed(runnable, DELAY);
}
};
handler.postDelayed(runnable, DELAY);
return true;
}
});
Here the runnable object is reused. And when the view is not pressed anymore, it will stop calling itself.
The decrease view or button can be defined in a similar way.
I am late to answer this question but it may help any one who's looking for a better answer.
I created a CounterHandler class and its insanely easy to use to achieve the above mentioned continuous counter functionality.
You can find the class in following gist with a "how to use" example.
https://gist.github.com/nomanr/d142f4ccaf55ceba22e7f7122b55b9b6
Sample code
new CounterHandler.Builder()
.incrementalView(buttonPlus)
.decrementalView(buttonMinus)
.minRange(-50) // cant go any less than -50
.maxRange(50) // cant go any further than 50
.isCycle(true) // 49,50,-50,-49 and so on
.counterDelay(200) // speed of counter
.counterStep(2) // steps e.g. 0,2,4,6...
.listener(this) // to listen counter results and show them in app
.build();
Thats all. :)
My way to increment value on long click is to use Timer used to check periodically if button is still pressed and than increase value, otherwise cancel timer. To update UI use Handler.
vh.bttAdd.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if(vh.bttAdd.isPressed()) {
final int track = ((ChannelAudioTrack) channels.get(vh.getAdapterPosition())).goToNextTrack();
updateUI(vh,track);
}
else
timer.cancel();
}
},100,200);
return true;
}
});
Handler:
private void updateUI(final TrackViewHolder vh, final int track)
{
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
vh.tvTrackNumber.setText(Integer.toString(track));
}
}) ;
}
Just wanna share my own solution that worked out for me really well.
First, create a handler in your activity
private Handler mHandler = new Handler();
Then, create the runnables that will increment/decrement and display your number. In here, we will check if your button is still in its pressed state and increment then re-run the runnable if it is.
private Runnable incrementRunnable = new Runnable() {
#Override
public void run() {
mHandler.removeCallbacks(incrementRunnable); // remove our old runnable, though I'm not really sure if this is necessary
if(IncrementButton.isPressed()) { // check if the button is still in its pressed state
// increment the counter
// display the updated value here, if necessary
mHandler.postDelayed(incrementRunnable, 100); // call for a delayed re-check of the button's state through our handler. The delay of 100ms can be changed as needed.
}
}
}
Finally, use it in our button's onLongClickListener
IncrementButton.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
mHandler.postDelayed(incrementRunnable, 0); // initial call for our handler.
return true;
}
});
That's it!
Another way of doing it is declaring both the handler and runnable inside the OnLongClickListener itself although I myself am not sure if this is a good practice.
IncrementButton.setOnLongClickListener(new View.OnLongClickListener() {
private Handler mHandler = Handler();
private Runnable incrementRunnable = new Runnable() {
#Override
public void run() {
mHandler.removeCallbacks(incrementRunnable);
if(IncrementButton.isPressed()) {
// increment the counter
// display the updated value here, if necessary
mHandler.postDelayed(incrementRunnable, 100);
}
}
};
#Override
public boolean onLongClick(View view) {
mHandler.postDelayed(incrementRunnable, 0);
return true;
}
});
When doing this continuous increment, I would suggest to increase the increment value after a certain time/number of increments.
E.g. If the number_of_increment made is less than 10, we increment by 1. Otherwise, we increment by 3.
It seems there is no perfect solution to this question and there will always be some complexity involved.
This is my attempt, which incorporates Wiktor's answer, but gives a whole MainActivity that you can cut/paste.
In my example, the complex part is the onLongClickListener, and how deep it goes and how many levels of anonymous classes there are.
However, on the other hand, the simplicity is that everything is included in one relatively short class (MainActivity), and there is only one major block of code -- the onLongClickListener -- which is defined only once and it's very clear where the "action" code is:
package com.example.boober.aalongclickoptimizationunit;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends AppCompatActivity {
TextView valueDisplay;
Button minusButton;
Button plusButton;
Button[] arrayOfControlButtons;
Integer currentDisplayValue = 500;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
valueDisplay = findViewById(R.id.value);
minusButton = findViewById(R.id.minusButton);
plusButton = findViewById(R.id.plusButton);
arrayOfControlButtons = new Button[]{plusButton, minusButton}; // this could be a large set of buttons
updateDisplay(); // initial setting of display
for (Button b : arrayOfControlButtons) {
b.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(final View v) {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if (v.isPressed()) { // important: checking if button still pressed
runOnUiThread(new Runnable() {
#Override
public void run() {
// --------------------------------------------------
// this is code that runs each time the
// long-click timer "goes off."
switch (v.getId()) {
// which button was pressed?
case R.id.plusButton: {
currentDisplayValue = currentDisplayValue + 10;
break;
}
case R.id.minusButton: {
currentDisplayValue = currentDisplayValue - 10;
break;
}
}
updateDisplay();
// --------------------------------------------------
}
});
} else
timer.cancel();
}
}, 100, 200);
// if set to false, then long clicks will propagate into single-clicks
// also, and we don't want that.
return true;
}
});
}
}
// ON-CLICKS (referred to from XML)
public void minusButtonPressed(View ignored) {
currentDisplayValue--;
updateDisplay();
}
public void plusButtonPressed(View ignored) {
currentDisplayValue++;
updateDisplay();
}
// INTERNAL
private void updateDisplay() {
valueDisplay.setText(currentDisplayValue.toString());
}
}
for kotlin user
myButton.setOnLongClickListener {
val handler = Handler(Looper.myLooper()!!)
val runnable : Runnable = object : Runnable {
val number = 0
override fun run() {
handler.removeCallbacks(this)
if (myButton.isPressed) {
val newNumber= number + 1
textView.text = "$newNumber Items"
handler.postDelayed(this, 100)
}
}
}
handler.postDelayed(runnable,0)
true
}
Version for Kotlin and Coroutines users (replace GlobalScope with any other scope if you'd like):
var job: Job? = null
viewClickable.setOnClickListener {
// single click
}
viewClickable.setOnLongClickListener {
if (job == null || !job!!.isActive) {
job = GlobalScope.launch(Dispatchers.Main.immediate) {
while (it.isPressed) {
// long press
delay(100)
}
}
}
true
}
import java.awt.;
import java.awt.event.;
public class LabelNumber extends Frame implements ActionListener
{
Button badd,bsub;
TextField t1;
void display()
{
setTitle("Label number change");
setSize(400,500);
setLayout(new FlowLayout());
setVisible(true);
badd=new Button("+");
t1=new TextField("0",6);
bsub= new Button("-");
add(bsub);add(t1);add(badd);
badd.addActionListener(this);
bsub.addActionListener(this);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
}
);
}
public void actionPerformed(ActionEvent e)
{
int count=0,add=0,sub,n1,n2;
count=Integer.parseInt(t1.getText());
if(e.getSource()==badd)
{
if(count<10)
{
count=count+1;
t1.setText(""+count);
}
else
{
t1.setText("limit:10");
}
}
if(e.getSource()==bsub)
{
if(count<10)
{
count=count-1;
t1.setText(""+count);
}
else
{
t1.setText("limit:10");
}
}
}
public static void main(String args[])
{
LabelNumber obj =new LabelNumber();
obj.display();
}
}
initlize and call method
int speed = 0;
button = findViewById(R.id.button);
button.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
v.setPressed(true);
increment();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
v.setPressed(false);
speed = 0;
}
return true;
});
This is increase method
public void increment() {
new Handler().postDelayed(() -> {
Toast.makeText(FrequencyActivity.this, String.valueOf(speed), Toast.LENGTH_SHORT).show();
if (button.isPressed()) {
speed += 1;
increment();
}
}, 200); //200 ms for fast incrementing
}
To break this task into its basic requirements, this is what we need:
Function to be executed
Condition for re-execution (as function that can be called each time to check condition)
Delay before re-execution
Here is a function that can be copied into Utils class, and be called for anything that matches these requirements:
/**
* Execute given function, and if condition is met, re-execute recursively after delay
* #param function: function to be executed
* #param conditionToRepeat: condition to re-execute function
* #param delayMillis: delay after which function should be re-executed (if condition was met)
*/
fun executeRecursively(function: () -> Unit, conditionToRepeat: () -> Boolean, delayMillis: Long) {
function()
if (conditionToRepeat())
Handler(Looper.getMainLooper()).postDelayed(
{ executeRecursively(function, conditionToRepeat, delayMillis) },
delayMillis)
}
Example of usage for requested use-case:
binding.button.setOnLongClickListener {
executeRecursively( { increase() }, // function
{ binding.button.isPressed }, // condition to re-execute
DELAY // delay in millis before re-execution
)
true
}
This is all what you need. But many cases, you may want to decrease delay when long clicking, so the number will be increased/decreased faster. Here is an extended function for that case:
/**
* Execute given function, and if condition is met, re-execute recursively after delay
* #param function: function to be executed
* #param conditionToRepeat: condition to re-execute function
* #param delayMillis: delay after which function should be re-executed (if condition was met)
* #param minDelayMillis: minimal delay in milliseconds
* #param decreasingDelayMillis: amount to decrease delay for next re-execution (if minimal delay has not been reached)
*/
fun executeRecursivelyWithDecreasingDelay(function: () -> Unit, conditionToRepeat: () -> Boolean, delayMillis: Long, minDelayMillis: Long, decreasingDelayMillis: Long) {
function()
if (conditionToRepeat()) {
val delay = if (delayMillis <= minDelayMillis) minDelayMillis else delayMillis - decreasingDelayMillis
Handler(Looper.getMainLooper()).postDelayed(
{ executeRecursivelyWithDecreasingDelay(function, conditionToRepeat, delay, minDelayMillis, decreasingDelayMillis) },
delayMillis
)
}
}
In this function, just add minimal delay (for example 2 millis), and the rate in which delay should be decreased each time (for example 2 millis). decreasingDelayMillis is like delay negative velocity.
Best and easy solution i created ,check it out it works for me
public void increment() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
check_amount = check_amount + 100;// increment by 100
tv_balance.setText("" + check_amount); // show continues incrementing value
if (check_amount != 5000) {
increment();
}
}
}, 1); //1 ms for fast incrementing
}
Related
Firing an event as soon as activity is visible to the user
I wanted to create countdown as soon as the user sees an activity. However, there does not seem to be an appropriate callback for that. Neither onResume nor onWindowFocusChanged seem to be the correct callbacks, because the whole code is executed before the user even see anything. As a result I end up with "Go!" on the screen right from the start. In a nutshell: Do you have any idea how to implement a countdown without any user interaction as soon as the activity is visible to the user? EDIT: import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import java.util.concurrent.TimeUnit; import android.widget.TextView; public class ChallengeModeTutorial extends AppCompatActivity { private void delayOneSec() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { assert true; } } #Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_challenge_mode_tutorial); } #Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); TextView readySteadyGo = (TextView) findViewById(R.id.challengeModeTutorialReadySteadyGoTextView); // TextView tutorialText = (TextView) findViewById(R.id.challengeModeTutorialTextTextView); TextView timeUntilStart = (TextView) findViewById(R.id.challengeModeTutorialReadyTimeTextView); readySteadyGo.setText(""); timeUntilStart.setText("5"); delayOneSec(); timeUntilStart.setText("4"); delayOneSec(); timeUntilStart.setText("3"); delayOneSec(); readySteadyGo.setText("Ready!"); timeUntilStart.setText("2"); delayOneSec(); readySteadyGo.setText("Steady!"); timeUntilStart.setText("1"); delayOneSec(); readySteadyGo.setText(""); readySteadyGo.setText("Go!"); } }
The problem is that you're blocking the UI thread. When you call setText(String) it doesn't immediately gets drawn. The TextView gets invalidated and it will be draw on the next draw phase. But if you block the thread, this will never happen. You have to use a postDelayed() to execute the next setText(String) a second later.
I'm not sure if this works, but you could try using a ViewTreeObserver in onCreate. It gets called once the layout is drawn. (Replace LinearLayout with whatever Layout your "activity_challenge_mode_tutorial" actually is.) #Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_challenge_mode_tutorial); final LinearLayout layout = (LinearLayout) findViewById(R.id.YOUR_VIEW_ID); ViewTreeObserver vto = layout.getViewTreeObserver(); vto.addOnGlobalLayoutListener (new OnGlobalLayoutListener() { #Override public void onGlobalLayout() { // Remove listener to prevent repeat calls. layout.getViewTreeObserver().removeOnGlobalLayoutListener(this); // Start countdown here. } }); } Code is taken from this answer: https://stackoverflow.com/a/7735122/8118328
You can start your countdown, inside of your onStart method, which you need to overwrite. The visible lifetime of an activity happens between a call to onStart() until a corresponding call to onStop(). During this time the user can see the activity on-screen, though it may not be in the foreground and interacting with the user. From: https://developer.android.com/reference/android/app/Activity #Override protected void onStart() { super.onStart(); }
I finally got it to work like this: import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class ChallengeModeTutorial extends AppCompatActivity { #Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_challenge_mode_tutorial); } #Override protected void onStart() { super.onStart(); final TextView readySteadyGo = (TextView) findViewById(R.id.challengeModeTutorialReadySteadyGoTextView); final TextView timeUntilStart = (TextView) findViewById(R.id.challengeModeTutorialReadyTimeTextView); readySteadyGo.setText(""); Thread t=new Thread(){ #Override public void run(){ while(continueThread){ try { Thread.sleep(1000); runOnUiThread(new Runnable() { #Override public void run() { if(currentCount > 0) { timeUntilStart.setText(String.valueOf(currentCount)); } else { timeUntilStart.setText("Go!"); } switch (currentCount) { case 2: readySteadyGo.setText("Ready!"); break; case 1: readySteadyGo.setText("Steady!"); break; default: readySteadyGo.setText(""); break; } currentCount--; if (currentCount == 0) { continueThread = false; } } }); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t.start(); } private boolean continueThread = true; private int currentCount = 5; }
How show ProgressBar update with Volley Android
I have a problem with showing progress on ProgressBar. I have function that calls my server with Volley, when return result I have implemented a Callback that return data to my activity. Something like: public static void saveData(Data data, final VolleyCallbackJsonObject callback){ ... callback.onSuccessResponse(new JSONObject()); ... } Supposing that the function is called on cycle like: for(int i = 1; i<=5; i++){ final int copyIndex = i; MyClass.saveData(new Data(i), new VolleyCallbackJsonObject() { #Override public void onSuccessResponse(JSONObject result) { callFunctionForShowAndUpdateProgress(copyIndex); } }) } } I would like show a ProgressBar that show a bar with a progress from 1/5 to 5/5 so I have my Function in my activityClass: public Class Test extends Activity{ private ProgressBar pb; public void callFunctionForShowAndUpdateProgress(int index){ if(index == 1){ pb = (ProgressBar) findViewById(R.id.pbLoading); pb.setMax(5); pb.setIndeterminate(false); pb.setVisibility(View.VISIBLE); } if(pb != null){ pb.setProgress(index); } if(index == 5){ pb.postDelayed(new Runnable() { #Override public void run() { pb.setVisibility(View.GONE); } }, 3000); } } } My problem is that progress bar not set progress, but it show only at the end so with 5/5 and not show the progress step (1/5, 2/5, 3/5, 4/5) and then it will hide after 3 seconds how specified on postDelayed. So How can I show all the step? what is wrong?
Try to postDelay the method 'callFunctionForShowAndUpdateProgress' in the method 'onSuccessResponse' instead the postDelay you are already making because the delay you are making happens only when i = 5, so technically the progress goes through 2, 3, and 4 but you don't see it. try this inside 'onSuccessResponse': Handler handler = new Handler(); handler.postDelayed(new Runnable() { #Override public void run() { callFunctionForShowAndUpdateProgress(copyIndex); } }, 3000); By the way, When the method 'callFunctionForShowAndUpdateProgress' is called in the 'onSuccessResponse' the index initially will be 1 and that is fine, the compiler then will go to second if statement if(pb != null){ pb.setProgress(index); } and will execute it because the condition is correct there, it will not cause a problem but leaving it like this is not a good practice, to solve this prevent the compiler from getting into this if statement by adding else. I hope that helps
Change your cycle calling function as like below and then check, int copyIndex = 1; callFunctionForShowAndUpdateProgress(copyIndex); for(int i = 1; i<=5; i++) { MyClass.saveData(new Data(i), new VolleyCallbackJsonObject() { #Override public void onSuccessResponse(JSONObject result) { copyIndex++; callFunctionForShowAndUpdateProgress(copyIndex); } }) }
Android: Running thread preventing animation from starting
I currently have an Android activity which manages some locally-stored RSS feeds. In this activity, these feeds are updated in their own thread via a private class. I'm also trying to include an "updating" icon that rotates with a RotateAnimation while this thread is running. The animation works by itself, but doesn't work while the thread is running despite the log entries stating the code is being executed. I suspect this is due to the thread not being entirely safe, and taking up most of the CPU time. However I'd just like to know if there's a better way of achieving this. The function updateAllFeeds() is called from a button press. Here's the relevant code: /** * Gets the animation properties for the rotation */ protected RotateAnimation getRotateAnimation() { // Now animate it Log.d("RSS Alarm", "Performing animation"); RotateAnimation anim = new RotateAnimation(359f, 0f, 16f, 21f); anim.setInterpolator(new LinearInterpolator()); anim.setRepeatCount(Animation.INFINITE); anim.setDuration(700); return anim; } /** * Animates the refresh icon with a rotate */ public void setUpdating() { btnRefreshAll.startAnimation(getRotateAnimation()); } /** * Reverts the refresh icon back to a still image */ public void stopUpdating() { Log.d("RSS Alarm", "Stopping animation"); btnRefreshAll.setAnimation(null); refreshList(); } /** * Updates all RSS feeds in the list */ protected void updateAllFeeds() { setUpdating(); Updater updater = new Updater(channels); updater.run(); } /** * Class to update RSS feeds in a new thread * #author Michael * */ private class Updater implements Runnable { // Mode flags public static final int MODE_ONE = 0; public static final int MODE_ALL = 1; // Class vars Channel channel; ArrayList<Channel> channelList; int mode; /** * Constructor for singular update * #param channel */ public Updater(Channel channel) { this.mode = MODE_ONE; this.channel = channel; } /** * Constructor for updating multiple feeds at once * #param channelList The list of channels to be updated */ public Updater(ArrayList<Channel> channelList) { this.mode = MODE_ALL; this.channelList = channelList; } /** * Performs all the good stuff */ public void run() { // Flag for writing problems boolean write_error = false; // Check if we have a singular or list if(this.mode == MODE_ONE) { // Updating one feed only int updateStatus = channel.update(getApplicationContext()); // Check for error if(updateStatus == 2) { // Error - show dialog write_error = true; } } else { // Iterate through all feeds for(int i = 0; i < this.channelList.size(); i++) { // Update this item int updateStatus = channelList.get(i).update(getApplicationContext()); if(updateStatus == 2) { // Error - show dialog write_error = true; } } } // If we have an error, show the dialog if(write_error) { runOnUiThread(new Runnable(){ public void run() { showDialog(ERR_SD_READ_ONLY); } }); } // End updater stopUpdating(); } // End run() } // End class Updater (I know the updateStatus == 2 bit is bad practice, that's one of the next things I plan to tidy up). Any help is greatly appreciated, many thanks in advance.
Run the updater runnable in a separate thread. Do the following changes. protected void updateAllFeeds() { setUpdating(); new Thread( new Updater(channels)).start(); } Call stopUpdating() in runOnUiThread block. private class Updater implements Runnable { ......... ......... ......... public void run() { ......... ......... // End updater runOnUiThread(new Runnable(){ public void run() { stopUpdating(); } }); } // End run() } // End class Updater
Move anything that affects the UI off into its own Runnable and then post it with your button btnRefreshAll.post(new StopUpdating());
I managed to get this working last night using Android's AsyncTask class. It was surprisingly easy to implement, though the downside was I had to write one class for updating an individual feed, and another for updating all feeds. Here's the code for updating all feeds at once: private class MassUpdater extends AsyncTask<ArrayList<Channel>, Void, Void> { #Override protected Void doInBackground(ArrayList<Channel>... channels) { ArrayList<Channel> channelList = channels[0]; // Flag for writing problems boolean write_error = false; // Iterate through all feeds for(int i = 0; i < channelList.size(); i++) { // Update this item int updateStatus = channelList.get(i).update(getApplicationContext()); if(updateStatus == FileHandler.STATUS_WRITE_ERROR) { // Error - show dialog write_error = true; } } // If we have an error, show the dialog if(write_error) { runOnUiThread(new Runnable(){ public void run() { showDialog(ERR_SD_READ_ONLY); } }); } return null; } protected void onPreExecute() { btnRefreshAll.setAnimation(getRotateAnimation()); btnRefreshAll.invalidate(); btnRefreshAll.getAnimation().startNow(); } protected void onPostExecute(Void hello) { btnRefreshAll.setAnimation(null); refreshList(); } } Thanks for your answers guys. userSeven7s' reply also makes a lot of sense so I may use that as a backup should I run into any problems with AsyncTask.
How to cancel an AsyncTask and then to instantiate a new one with a single click?
I have a custom DownloadFiles class which extends AsyncTask. The app launches itself and starts an AsyncTask in background for a default URL. On the UI, I have a number of buttons - each of them onClick event should cancel the current DownloadFiles instance and create new one with a different URL. private OnClickListener categoryClick = new OnClickListener(){ public void onClick(View view) { dft.cancel(true); int index = catigoriesHolder.indexOfChild(view); activeCategory.setText(categories[index]); dft = new DownloadFilesTask(); dft.execute(rssFeedURL[index]); dft.execute(url); } }; dft is my DownloadFiles object, witch I need to cancel. And here is my onCancelled method: protected void onCancelled() { super.onCancelled(); } I tried also to check inside the click-event if the dft is cancelled and then to create a new instance of my custom downloadable class. How could be this done and why when I trying this: private OnClickListener categoryClick = new OnClickListener(){ public void onClick(View view) { if (dft.isCancelled()) { int index = catigoriesHolder.indexOfChild(view); activeCategory.setText(categories[index]); dft = new DownloadFilesTask(); dft.execute(rssFeedURL[index]); } else { dft.cancel(true); } } }; it does not work, even on the second click? I tried this after you advised me to check for isCancelled() inside the doInBackground(...) private OnClickListener categoryClick = new OnClickListener(){ public void onClick(View view) { int index = catigoriesHolder.indexOfChild(view); activeCategory.setText(categories[index]); if (dft.isCancelled()) { dft.cancel(false); dft = new DownloadFilesTask(); dft.execute(rssFeedURL[index]); } else { dft.cancel(true); } } }; But what happens, in this case: - the default URL works fine, but when I click on a button, there is no action. protected List<RSSItem> doInBackground(String... urls) { parse(urls[0]); if (feed == null) { return null; } rssList = feed.getAllItems(); Iterator<RSSItem> iterator = rssList.iterator(); while(iterator.hasNext()) { if (isCancelled()) return null; RSSItem rss = iterator.next(); publishProgress(rss); } return rssList; } After I checked the status of the current AsyncTask, it works: private OnClickListener categoryClick = new OnClickListener(){ public void onClick(View view) { String status = dft.getStatus().name(); if (status.equals("FINISHED") || dft.isCancelled()) { dft = new DownloadFilesTask(); int index = catigoriesHolder.indexOfChild(view); activeCategory.setText(categories[index]); dft.execute(rssFeedURL[index]); } else { dft.cancel(true); } } }; But still doesn't fit my expectations. Currently you should click twice to get new instance of DownloadFiles class. Could to transform it to a single click?
The .cancel(true) method only changes the internal canceled variable in the task. You have to call isCancelled() inside doInBackground() periodically and return when it evaluates true. Also, please specify what is not working in each case.
Android AnimationDrawable and knowing when animation ends
I want to do an animation with several image-files, and for this the AnimationDrawable works very well. However, I need to know when the animation starts and when it ends (i.e add a listener like the Animation.AnimationListener). After having searched for answers, I'm having a bad feeling the AnimationDrawable does not support listeners.. Does anyone know how to create a frame-by-frame image animation with a listener on Android?
After doing some reading, I came up with this solution. I'm still surprised there isn't a listener as part of the AnimationDrawable object, but I didn't want to pass callbacks back and forward so instead I created an abstract class which raises an onAnimationFinish() method. I hope this helps someone. The custom animation drawable class: public abstract class CustomAnimationDrawableNew extends AnimationDrawable { /** Handles the animation callback. */ Handler mAnimationHandler; public CustomAnimationDrawableNew(AnimationDrawable aniDrawable) { /* Add each frame to our animation drawable */ for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) { this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i)); } } #Override public void start() { super.start(); /* * Call super.start() to call the base class start animation method. * Then add a handler to call onAnimationFinish() when the total * duration for the animation has passed */ mAnimationHandler = new Handler(); mAnimationHandler.post(new Runnable() { #Override public void run() { onAnimationStart(); } }; mAnimationHandler.postDelayed(new Runnable() { #Override public void run() { onAnimationFinish(); } }, getTotalDuration()); } /** * Gets the total duration of all frames. * * #return The total duration. */ public int getTotalDuration() { int iDuration = 0; for (int i = 0; i < this.getNumberOfFrames(); i++) { iDuration += this.getDuration(i); } return iDuration; } /** * Called when the animation finishes. */ public abstract void onAnimationFinish(); /** * Called when the animation starts. */ public abstract void onAnimationStart(); } To use this class: ImageView iv = (ImageView) findViewById(R.id.iv_testing_testani); iv.setOnClickListener(new OnClickListener() { public void onClick(final View v) { // Pass our animation drawable to our custom drawable class CustomAnimationDrawableNew cad = new CustomAnimationDrawableNew( (AnimationDrawable) getResources().getDrawable( R.drawable.anim_test)) { #Override void onAnimationStart() { // Animation has started... } #Override void onAnimationFinish() { // Animation has finished... } }; // Set the views drawable to our custom drawable v.setBackgroundDrawable(cad); // Start the animation cad.start(); } });
I needed to know when my one-shot AnimationDrawable completes, without having to subclass AnimationDrawable since I must set the animation-list in XML. I wrote this class and tested it on Gingerbread and ICS. It can easily be extended to give a callback on each frame. /** * Provides a callback when a non-looping {#link AnimationDrawable} completes its animation sequence. More precisely, * {#link #onAnimationComplete()} is triggered when {#link View#invalidateDrawable(Drawable)} has been called on the * last frame. * * #author Benedict Lau */ public abstract class AnimationDrawableCallback implements Callback { /** * The last frame of {#link Drawable} in the {#link AnimationDrawable}. */ private Drawable mLastFrame; /** * The client's {#link Callback} implementation. All calls are proxied to this wrapped {#link Callback} * implementation after intercepting the events we need. */ private Callback mWrappedCallback; /** * Flag to ensure that {#link #onAnimationComplete()} is called only once, since * {#link #invalidateDrawable(Drawable)} may be called multiple times. */ private boolean mIsCallbackTriggered = false; /** * * #param animationDrawable * the {#link AnimationDrawable}. * #param callback * the client's {#link Callback} implementation. This is usually the {#link View} the has the * {#link AnimationDrawable} as background. */ public AnimationDrawableCallback(AnimationDrawable animationDrawable, Callback callback) { mLastFrame = animationDrawable.getFrame(animationDrawable.getNumberOfFrames() - 1); mWrappedCallback = callback; } #Override public void invalidateDrawable(Drawable who) { if (mWrappedCallback != null) { mWrappedCallback.invalidateDrawable(who); } if (!mIsCallbackTriggered && mLastFrame != null && mLastFrame.equals(who.getCurrent())) { mIsCallbackTriggered = true; onAnimationComplete(); } } #Override public void scheduleDrawable(Drawable who, Runnable what, long when) { if (mWrappedCallback != null) { mWrappedCallback.scheduleDrawable(who, what, when); } } #Override public void unscheduleDrawable(Drawable who, Runnable what) { if (mWrappedCallback != null) { mWrappedCallback.unscheduleDrawable(who, what); } } // // Public methods. // /** * Callback triggered when {#link View#invalidateDrawable(Drawable)} has been called on the last frame, which marks * the end of a non-looping animation sequence. */ public abstract void onAnimationComplete(); } Here is how to use it. AnimationDrawable countdownAnimation = (AnimationDrawable) mStartButton.getBackground(); countdownAnimation.setCallback(new AnimationDrawableCallback(countdownAnimation, mStartButton) { #Override public void onAnimationComplete() { // TODO Do something. } }); countdownAnimation.start();
Animation end can be easily tracked by overriding selectDrawable method in AnimationDrawable class. Complete code is the following: public class AnimationDrawable2 extends AnimationDrawable { public interface IAnimationFinishListener { void onAnimationFinished(); } private boolean finished = false; private IAnimationFinishListener animationFinishListener; public IAnimationFinishListener getAnimationFinishListener() { return animationFinishListener; } public void setAnimationFinishListener(IAnimationFinishListener animationFinishListener) { this.animationFinishListener = animationFinishListener; } #Override public boolean selectDrawable(int idx) { boolean ret = super.selectDrawable(idx); if ((idx != 0) && (idx == getNumberOfFrames() - 1)) { if (!finished) { finished = true; if (animationFinishListener != null) animationFinishListener.onAnimationFinished(); } } return ret; } }
I used a recursive function that checks to see if the current frame is the last frame every timeBetweenChecks milliseconds. private void checkIfAnimationDone(AnimationDrawable anim){ final AnimationDrawable a = anim; int timeBetweenChecks = 300; Handler h = new Handler(); h.postDelayed(new Runnable(){ public void run(){ if (a.getCurrent() != a.getFrame(a.getNumberOfFrames() - 1)){ checkIfAnimationDone(a); } else{ Toast.makeText(getApplicationContext(), "ANIMATION DONE!", Toast.LENGTH_SHORT).show(); } } }, timeBetweenChecks); }
A timer is a bad choice for this because you will get stuck trying to execute in a non UI thread like HowsItStack said. For simple tasks you can just use a handler to call a method at a certain interval. Like this: handler.postDelayed(runnable, duration of your animation); //Put this where you start your animation private Handler handler = new Handler(); private Runnable runnable = new Runnable() { public void run() { handler.removeCallbacks(runnable) DoSomethingWhenAnimationEnds(); } }; removeCallbacks assures this only executes once.
This is so simple when it come to using Kotlin, AnimationDrawable has two functions we could use to calculate the animation duration, then we could add a runnable with delay to create an Animation listener. here is a simple Kotlin extension. fun AnimationDrawable.onAnimationFinished(block: () -> Unit) { var duration: Long = 0 for (i in 0..numberOfFrames) { duration += getDuration(i) } Handler().postDelayed({ block() }, duration + 200) }
if you want to impliment your animation in adapter - should use next public class CustomAnimationDrawable extends AnimationDrawable { /** * Handles the animation callback. */ Handler mAnimationHandler; private OnAnimationFinish onAnimationFinish; public void setAnimationDrawable(AnimationDrawable aniDrawable) { for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) { this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i)); } } public void setOnFinishListener(OnAnimationFinish onAnimationFinishListener) { onAnimationFinish = onAnimationFinishListener; } #Override public void stop() { super.stop(); } #Override public void start() { super.start(); mAnimationHandler = new Handler(); mAnimationHandler.postDelayed(new Runnable() { public void run() { if (onAnimationFinish != null) onAnimationFinish.onFinish(); } }, getTotalDuration()); } /** * Gets the total duration of all frames. * * #return The total duration. */ public int getTotalDuration() { int iDuration = 0; for (int i = 0; i < this.getNumberOfFrames(); i++) { iDuration += this.getDuration(i); } return iDuration; } /** * Called when the animation finishes. */ public interface OnAnimationFinish { void onFinish(); } } and implementation in RecycleView Adapter #Override public void onBindViewHolder(PlayGridAdapter.ViewHolder holder, int position) { final Button mButton = holder.button; mButton.setBackgroundResource(R.drawable.animation_overturn); final CustomAnimationDrawable mOverturnAnimation = new CustomAnimationDrawable(); mOverturnAnimation.setAnimationDrawable((AnimationDrawable) mContext.getResources().getDrawable(R.drawable.animation_overturn)); mOverturnAnimation.setOnFinishListener(new CustomAnimationDrawable.OnAnimationFinish() { #Override public void onFinish() { // your perform } }); mButton.setOnClickListener(new View.OnClickListener() { #Override public void onClick(final View v) { mOverturnAnimation.start(); } }); }
I also like Ruslan's answer, but I had to make a couple of changes in order to get it to do what I required. In my code, I have got rid of Ruslan's finished flag, and I have also utilised the boolean returned by super.selectDrawable(). Here's my code: class AnimationDrawableWithCallback extends AnimationDrawable { interface IAnimationFinishListener { void onAnimationChanged(int index, boolean finished); } private IAnimationFinishListener animationFinishListener; public IAnimationFinishListener getAnimationFinishListener() { return animationFinishListener; } void setAnimationFinishListener(IAnimationFinishListener animationFinishListener) { this.animationFinishListener = animationFinishListener; } #Override public boolean selectDrawable(int index) { boolean drawableChanged = super.selectDrawable(index); if (drawableChanged && animationFinishListener != null) { boolean animationFinished = (index == getNumberOfFrames() - 1); animationFinishListener.onAnimationChanged(index, animationFinished); } return drawableChanged; } } And here is an example of how to implement it... public class MyFragment extends Fragment implements AnimationDrawableWithCallback.IAnimationFinishListener { #Override public void onAnimationChanged(int index, boolean finished) { // Do whatever you need here } } If you only want to know when the first cycle of animation has completed, then you can set a boolean flag in your fragment/activity.
I guess your Code does not work, because you try to modify a View from a non-UI-Thread. Try to call runOnUiThread(Runnable) from your Activity. I used it to fade out a menu after an animation for this menu finishes. This code works for me: Animation ani = AnimationUtils.loadAnimation(YourActivityNameHere.this, R.anim.fadeout_animation); menuView.startAnimation(ani); // Use Timer to set visibility to GONE after the animation finishes. TimerTask timerTask = new TimerTask(){ #Override public void run() { YourActivityNameHere.this.runOnUiThread(new Runnable(){ #Override public void run() { menuView.setVisibility(View.GONE); } });}}; timer.schedule(timerTask, ani.getDuration());
You can use registerAnimationCallback to check your animation start and end. Here's the snippet code: // ImageExt.kt fun ImageView.startAnim(block: () -> Unit) { (drawable as Animatable).apply { registerAnimationCallback( drawable, object : Animatable2Compat.AnimationCallback() { override fun onAnimationStart(drawable: Drawable?) { block.invoke() isClickable = false isEnabled = false } override fun onAnimationEnd(drawable: Drawable?) { isClickable = true isEnabled = true } }) }.run { start() } } // Fragment.kt imageView.startAnim { // do something after the animation ends here } The purpose of the ImageExt was to disable after animation start (on progress) to prevent user spamming the animation and resulting in the broken / wrong vector shown. With frame-by-frame, you might want to trigger another ImageView like this. // Animation.kt iv1.startAnim { iv2.startAnim { // iv3, etc } } But the above solutions looks ugly. If anyone has a better approach, please comment below, or edit this answer directly.
I had the same problem when I had to implement a button click after animation stopped. I checked the current frame and the lastframe of animation drawable to know when an animation is stopped. Note that it is not a listener but just a way to know it animation has stopped. if (spinAnimation.getCurrent().equals( spinAnimation.getFrame(spinAnimation .getNumberOfFrames() - 1))) { Toast.makeText(MainActivity.this, "finished", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "Not finished", Toast.LENGTH_SHORT).show(); }
I don't know about all these other solutions, but this is the one that comes closest to simply adding a listener to the AnimationDrawable class. class AnimationDrawableListenable extends AnimationDrawable{ static interface AnimationDrawableListener { void selectIndex(int idx, boolean b); } public AnimationDrawableListener animationDrawableListener; public boolean selectDrawable(int idx) { boolean selectDrawable = super.selectDrawable(idx); animationDrawableListener.selectIndex(idx,selectDrawable); return selectDrawable; } public void setAnimationDrawableListener(AnimationDrawableListener animationDrawableListener) { this.animationDrawableListener = animationDrawableListener; } }
I prefer not to go for timing solution, as it seems to me isn't reliable enough. I love Ruslan Yanchyshyn's solution : https://stackoverflow.com/a/12314579/72437 However, if you notice the code carefully, we will receive animation end callback, during the animation start of last frame, not the animation end. I propose another solution, by using a dummy drawable in animation drawable. animation_list.xml <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true"> <item android:drawable="#drawable/card_selected_material_light" android:duration="#android:integer/config_mediumAnimTime" /> <item android:drawable="#drawable/card_material_light" android:duration="#android:integer/config_mediumAnimTime" /> <item android:drawable="#drawable/dummy" android:duration="#android:integer/config_mediumAnimTime" /> </animation-list> AnimationDrawableWithCallback.java import android.graphics.drawable.AnimationDrawable; /** * Created by yccheok on 24/1/2016. */ public class AnimationDrawableWithCallback extends AnimationDrawable { public AnimationDrawableWithCallback(AnimationDrawable aniDrawable) { /* Add each frame to our animation drawable */ for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) { this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i)); } } public interface IAnimationFinishListener { void onAnimationFinished(); } private boolean finished = false; private IAnimationFinishListener animationFinishListener; public void setAnimationFinishListener(IAnimationFinishListener animationFinishListener) { this.animationFinishListener = animationFinishListener; } #Override public boolean selectDrawable(int idx) { if (idx >= (this.getNumberOfFrames()-1)) { if (!finished) { finished = true; if (animationFinishListener != null) animationFinishListener.onAnimationFinished(); } return false; } boolean ret = super.selectDrawable(idx); return ret; } } This is how we can make use of the above class. AnimationDrawableWithCallback animationDrawable2 = new AnimationDrawableWithCallback(rowLayoutAnimatorList); animationDrawable2.setAnimationFinishListener(new AnimationDrawableWithCallback.IAnimationFinishListener() { #Override public void onAnimationFinished() { ... } }); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { view.setBackground(animationDrawable2); } else { view.setBackgroundDrawable(animationDrawable2); } // https://stackoverflow.com/questions/14297003/animating-all-items-in-animation-list animationDrawable2.setEnterFadeDuration(this.configMediumAnimTime); animationDrawable2.setExitFadeDuration(this.configMediumAnimTime); animationDrawable2.start();
i had used following method and it is really works. Animation anim1 = AnimationUtils.loadAnimation( this, R.anim.hori); Animation anim2 = AnimationUtils.loadAnimation( this, R.anim.hori2); ImageSwitcher isw=new ImageSwitcher(this); isw.setInAnimation(anim1); isw.setOutAnimation(anim2); i hope this will solve your problem.