I have 5 TextView fields that are meant to display a String and change the text every 10 seconds.
In order to make the change I have an AsyncTask that grabs the next 5 messages (doInBackground) and passes them to onPostExecute to be displayed in the UI.
When I display the messages without an animation, everything works perfectly fine and the messages rotate every 10 seconds via a new AsyncTask every time.
Now, when I attempt to use a simple fade-in/out animation on the TextViews, I can see all of them fading out and then back in with a placeholder string however the actual text messages I want to display are only displayed in the last TextView.
The AsyncTask is an inner-class of my MainActivity.java.
I have implemented an AnimationListener in MainActivity.java and due to that the objects below in onPostExecute ((TextView) mNextTextView & (String) mNextMessageToDisplay) are also declared as global variables in the main class.
Here's AsyncTask's onPostExecute:
protected void onPostExecute(List<String> mFiveMessagesToDisplay) {
super.onPostExecute(mFiveMessagesToDisplay);
for (int mTextViewCounter = 0; mTextViewCounter < 5; mTextViewCounter++) {
// Prepare the next TextView and message to use
mNextTextViewToUse = mTextViewArrayList.get(mTextViewCounter);
mNextMessageToDisplay = mFiveMessagesToDisplay.get(mTextViewCounter);
// Post and animate the message changes in the TextView
mNextTextViewToUse.startAnimation(mFadeOutAnimation);
}
}
And my AnimationListener:
private AnimationListener mFadeOutAnimListener = new AnimationListener() {
public void onAnimationStart(Animation animation) {}
public void onAnimationRepeat(Animation animation) {}
public void onAnimationEnd(Animation animation) {
mNextTextViewToUse.setVisibility(View.INVISIBLE);
mNextTextViewToUse.setText(mNextMessageToDisplay);
mNextTextViewToUse.setVisibility(View.VISIBLE);
mNextTextViewToUse.startAnimation(mFadeInAnimation);
}
}
As you can see, my setText() method is applied on the mNextTextViewToUse object with the contents of mNextMessageToDisplay, which are as mentioned before both global variables in the main class.
Both ArrayLists, the one containing the messages and the other containing the correct, I have iterated over both and printed the contents - everything is in its place.
Reminding again that if I use setText() with the same TextView and String objects under onPostExecute, everything works perfectly fine just without the animation.
Many thanks in advance!
Your issue is that the loops finishes by the time the animation ends, that's why you see only the last message.
Lets say that mNextTextViewToUse points to TextView1. You start a fade out animation for this TextView. By the time the animation ends, mNextTextViewToUse points to a different textView object (because the loop continue to run while the fade out animation is playing) so when onAnimationEnd is being called (by the fade out animation of TextView1) mNextTextView points to TextView5, and basically you are setting the text to this text view only.
There are several ways to solve this / implement what you need, personally I would probably create my own TextView class (extending TextView of course) and I'll add a public method called setTextAnimated (String newText). It will look something like:
public void setTextAnimated (String newText) {
mNextText = newText;
startAnimation(mFadeOutAnim);
}
where your onAnimationEnd callback will simple call setText(mNextText) and then will start fade-in animation.
Using this you will avoid another issue - that if you use the same animation object on different views - they will share the same timeline and interpolator. The result is that if View1 start a fade out animation that should take 200ms, and View 5 starts the same animation after 140ms, View5 will immediately "jump" to the same alpha value that View1 is.
I found the solution!
I ended up completely removing the FOR loop logic and using recursion which raises a Boolean flag once it has used all 5 available TextViews.
The method that gets the next TextView and String message and execute the animated setText():
public void updateTextView(List<String> mMessages) {
mNextTextViewToUse = mTextViewArrayList.get(mCurrentMessageIndex);
mNextMessageToDisplay = mMessages.get(mCurrentMessageIndex);
mNextTextViewToUse.startAnimation(mFadeOutAnimation);
if (mCurrentMessageIndex == 4) {
mStopMessageDisplayRepeat = true;
} else {
mCurrentMessageIndex++;
}
}
The Runnable that calls the method above, while the recursion stop Boolean is false:
public Runnable mUpdateTextViewRunnable = new Runnable() {
final static int POSTDELAY_INTERVAL = 600;
#Override
public void run() {
updateTextView(mFiveMessages);
if (! mStopMessageDisplayRepeat) {
mMessageDisplayRepeatHandler.postDelayed(mUpdateTextViewRunnable, POSTDELAY_INTERVAL);
}
}
};
Execute the Runnable:
public void startTextViewRunnableRepeat() {
mUpdateTextViewRunnable.run();
}
This is how my AsyncTask's onPostExecute() looks like:
#Override
protected void onPostExecute(List<String> mFiveMessagesToDisplay) {
super.onPostExecute(mFiveMessagesToDisplay);
mFiveMessages = mFiveMessagesToDisplay;
startTextViewRunnableRepeat();
}
Related
My problem is most likely because of bad design, but anyway :
I have an ArrayList that holds a bunch of ImageViews (terrain blocks).
Then, I iterated through to create the ImageViews programatically, and then set their animation to an animation called moveLeft which just makes the ImageView move left for 10 seconds.
Right after the animation ends, I need to set the ImageView's visibility to GONE because it will be off-screen by then.
The problem is that I used an int count to hold the total number of ImageViews.
moveLeft.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
terrainArray.get(count - 1).setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
I'm constantly making new terrain blocks, which means my count variable is constantly changing.
However, onAnimationEnd doesn't run with the same value of count as it does when the animation starts. As a result, it ends up setting some othhe block's visibility to GONE instead.
This is because onAnimationEnd didn't actually run until 10 seconds AFTER the block was created, because the animation lasts 10 seconds.
As a result, I'm left with the wrong block number when I want to set its visibility because MORE blocks are being created AS the animation is running. (therefore increasing count's value)
Is there some sort of way I could store count's current value on the onAnimationStart method
and then pass that same value to the onAnimationEnd method? (And make it work for up to infinite numbers of terrain blocks? Also, there's only one listener, and multiple blocks can possibly end their animation at the same time.)
This is easy. If you are sure that you need the exact value of count that is at the onAnimationStart(), then create a variable in the anonymous class. Save count value in onAnimationStart() and use it in onAnimationEnd()
You can copy the following snippet.
moveLeft.setAnimationListener(new Animation.AnimationListener() {
private int mCount = 0;
#Override
public void onAnimationStart(Animation animation) {
mCount = count;
}
#Override
public void onAnimationEnd(Animation animation) {
terrainArray.get(mCount - 1).setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
Hope this helps. But, I am afraid there could be bigger design problems. Be careful boss :)
I need execute a method when the fragment is visible (to the user).
Example:
I have 2 buttons (button 1 and button 2) ,
2 fragments(fragment 1 and fragment 2)
and the method loadImages() inside the class fragment 2.
when I press "button2" I want to replace fragment 1 by fragment 2
and then after the fragment 2 is visible (to the user) call loadImages().
I tried to use onResume() in the fragment class but it calls the method before the fragment is visible and it makes some delay to the transition.
I tried setUserVisibleHint() too and did not work.
A good example is the Instagram app. when you click on profile it loads the profile activity first and then import all the images.
I hope someone can help me. I will appreciate your help so much. Thank you.
Use the ViewTreeObserver callbacks:
#Override
public void onViewCreated(View v, Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
final View view = v;
// Add a callback to be invoked when the view is drawn
view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
#Override
public void onDraw() {
// Immediately detach the listener so it only is called once
view.getViewTreeObserver().removeOnDrawListener(this);
// You're visible! Do your stuff.
loadImages();
}
});
}
I'm a little confused by what you are trying to do. It sounds like the images are loading too fast for you... so does that mean that you have the images ready to display? And that is a bad thing?
My guess (and this is just a guess) is that Instagram does not have the profile pictures in memory, so they have to make an API call to retrieve them, which is why they show up on a delay. If the same is the case for you, consider starting an AsyncTask in the onResume method of the fragment. Do whatever loading you need to do for the images in the background, and then make the images appear in the onPostExecute callback on the main thread. Make sure you only start the task if the images are not already loaded.
However, if you already have the images loaded in memory, and you just want a delay before they appear to the user, then you can do a postDelayed method on Handler. Something like this:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
loadImages();
}
}, 1000);
Edit
As kcoppock points out, the handler code is pretty bad. I meant it to be a quick example, but it is so wrong I should not have included it in the first place. A more complete answer would be:
private Handler handler;
public void onResume(){
super.onResume();
if(handler == null){
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
loadImages();
}
}, 1000);
}
}
public void onDestroyView(){
super.onDestroyView();
handler.removeCallbacksAndMessages(null);
handler = null;
}
Use the onActivityCreated() callBck
I'm working on an app that downloads information from my server after an NFC card has been detected.
When a card is detected, I start
- an Asynctask to download some data from my server
- an animation of a popup appearing on the screen
After both the asynctask and the animation are done, I want to start a method that displays the downloaded data in the popup.
What is the correct way to trigger this new method? It can only start when both conditions are met.
In the async task you use to download data add the onPostExecute method to remove the animation popup and show the downloaded data as well like this :
protected void onPostExecute(Long result) {
//put code to disable animation popup
//code for displaying downloaded data popup
}
For more info check out http://developer.android.com/reference/android/os/AsyncTask.html
you Animation object has the method setAnimationLister. It takes as parameter a Class Object that implements the interface Animation.AnimationListener . This interface requires three methods to be implemented:
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
The onAnimationEnd is triggered when the animation ends. If I have not misunderstood you, this is what you need
EDIT:
you could have two booleans value, boolean animationFinished = false, downloadFinished = false; When onPostExecute is called put downloadFinished to true and call yourMethod.
When onAnimationEnd is triggered animationFinished = true and call yourMethod. yourMethod should start like:
if (!animationFinished || !downloadFinished)
return;
I have a long running method that is called during onCreate, this method populates textviews so interacts with the UI, and updates maybe 70 labels (about 3-20 seconds depending on device).
I want to display a progressdialog as this method executes.
Ideally I want to fire my method on the UI thread once the Activity has been displayed and the progress is displayed, this I cannot do, the Activity won't paint until the method has finished.
I hoped to find an event which was fired after the activity was displayed, and I found the one below, but it still leaves the screen black until the method has finished.
#Override
public void onPostCreate(Bundle savedInstanceState)
I am normally a WP7 developer, and in .NET you add an event handler for onLoadComplete which is fired after the ui is displayed, but before the user has a chance to interact withthe UI, how do I do this in Android JAVA?
Thanks
Put a ProgressBar in the View.
Then in the onCreate() or onResume() method do this:
new Thread() {
public void run() {
yourLargeMethod();
}
}.start();
Now you can do this inside your method to update the progressBar
public void yourLargeMethod() {
// doSomething
...
runOnUiThread(new Runnable() {
public void run() {
// update the progress from the thread
progressBar.setProgress(x); // x is your progress, 0 <= x <= progressBar.getMax()
}
});
}
someone can tell me how to update a control Textview Android from a function? I have searched deep into the internet and see many people who ask the same question, I tested threads but could not work, someone has a simple working example of doing this? for example to call a function (which runs several times in a loop) and the function writes in the TextView, but the problem is that until the function is not finished running, it shows me the text.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
while(condition) //here freezes the UI and the text in textview only shows when the loop ends
{
HardWork();
}
}
public void HardWork() {
txtProgreso.append("Test Line" + "\n\n");
};
Thanks in advance.
If i understood you correctly
Use AsyncTask here, So you can update textview in onProgressUpdate method
private class SomeTask extends AsyncTask<String, String, String> {
#Override
protected String doInBackground(String... params) {
while(condition) {
//do more
publishProgress("Some text value to you textview");
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
txtProgreso.append(values[0]);
}
}
I think what you're trying to ask is "how do I force my TextView to show its updated contents without returning control to the runloop?" (ie, returning out of your function).
If so, I'm afraid you're out of luck, such a thing is impossible to do with Android's UI model. The nearest thing to this that is possible is to keep track of your loop state, and set a timer that will call a function for you to update the TextView.
Here's an example. Suppose you want to put the text "this is a test" into a TextView, one character at a time:
private Handler mHandler = new Handler();
private String desiredText = new String("This is a test");
private Runnable mUpdateTextView = new Runnable() {
public void run() {
TextView textView = (TextView)findViewById(R.id.myTextView);
int lengthSoFar = textView.getText().length();
if (lengthSoFar < desiredText.length()) {
mTextView.setText(desiredText.substring(0, lengthSoFar + 1));
mHandler.postDelayed(100, mUpdateTextView);
}
}
};
protected void onStart() {
mHandler.postDelayed(100, mUpdateTextView);
}
I think some of my object visibility is incorrect here, and I don't have an Android environment handy to test on, but you get the general idea. Rather than a Handler you could also use a Timer, but it's a bit more heavyweight so it depends how often you want to be updating the UI.
You get the textview by doing the following.
TextView myTextView = (TextView)findViewById(R.id.textViewsId);
Then afterwards, you can change the TextView using the functions. For example, you can set the text using the following code.
myTextView.setText("New text example");
Are there any other updates to the control you are trying to do?