Playing frame animations in a sequence. Android - java

Ok, somewhat similar questions have been asked, but there is no answer that has made any difference in solving my problem. I've tried Thread.sleep, and also the delayed runnable. Using a Handler, etc.
I want to display a sequence of frame animations(AnimationDrawable) using the same imageview and changing the background animations as needed. The user inputs a series of 5 animations and can then play them back(if my program worked). Once the animations are selected I use a for loop that contains if statements and a switch statement to select that whatever animations were chosen and play them back.
As you might imagine, this doesn't work correctly, as the program whips through the for loop and only the first and last animation actually play. Here is the jist of the code:
for(i=0;i<5;i++){
if(conditions){
view.setBackground(chosenAnimation);
((AnimationDrawable)view.getBackground().start();
}
}
So as I said, I have tried Thread.sleep(), that doesn't do what I'm looking for. I have tried using the Handler class and putting a delay on the runnable. I have tried this:
view.postDelayed(new Runnable() {
#Override
public void run() {
//works the same without this line
((AnimationDrawable)view.getBackground()).stop();
((AnimationDrawable)view.getBackground()).start();
}
}, 1000);
None of these things do anything at all except add pauses before it does the exact same thing it did before I added this stuff. I have meticulously debugged the code and everything is working correctly. The animations have all been individually tested.
As I said, similar questions have been asked and answered and nothing offered does what I want, which is for the program to wait until one animation is finished before it runs through the for loop again.
I'd like to state again that this is a series of frame animations using AnimationDrawable and the same imageview each time. Thanks in advance!

All your animations will be started with the same delay, you should increase this delay by multiplying it by i for example. You also can count duration of every animation programmatically and increase delay as you need it.
I just tried to achieve what you want and had no problems, although my example uses 3rd party library, it's not necessary.
package com.example.masktest.app;
import android.animation.Animator;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicReference;
import static com.dtx12.android_animations_actions.actions.Actions.*;
public class MainActivity extends AppCompatActivity {
ImageView imageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.imageView);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
playAnimation();
}
});
}
private void playAnimation() {
final AnimationDrawable firstDrawable = (AnimationDrawable) ContextCompat.getDrawable(MainActivity.this, R.anim.anim_android);
final AnimationDrawable secondDrawable = (AnimationDrawable) ContextCompat.getDrawable(MainActivity.this, R.anim.anim_android_2);
final AtomicReference<Integer> cpt = new AtomicReference<>(0);
Animator sequence = repeat(6, sequence(run(new Runnable() {
#Override
public void run() {
if (imageView.getDrawable() instanceof AnimationDrawable) {
((AnimationDrawable) imageView.getDrawable()).stop();
}
imageView.setImageDrawable(cpt.get() % 2 == 0 ? secondDrawable : firstDrawable);
((AnimationDrawable) imageView.getDrawable()).start();
cpt.set(cpt.get() + 1);
}
}), delay(countAnimationDuration(secondDrawable))));
play(sequence, imageView);
}
private float countAnimationDuration(AnimationDrawable drawable) {
int duration = 0;
for (int i = 0; i < drawable.getNumberOfFrames(); i++) {
duration += drawable.getDuration(i);
}
return duration / 1000f;
}
}

You can simply using for loop and you dont need 3rd party library.
Lets say you have a LinearLayout(or any viewgroup) that you want to add these buttons dynamically. You can do something like this:
for (int i = 0; i < buttons.size(); i++) {
Button button = new Button(context);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
linearLayout.addView(button);
animateButton(button);
}
}, BUTTON_DELAY_DURATION * i);
}

Thanks to some assistance from dtx12 here, I have realized my problem, and I thought I'd leave some code here since I am not good enough to completely understand their example.
I didn't quite understand what I was doing making new threads, and it turns out I was just making 5 threads that all went off at the same time, as dtx12 explained and I eventually understood. So here's a basic way of doing this:
public void playAnimation(){
// in dtx12's answer they demonstrate how to get the
// exact duration of any give animation using
//getNumberOfFrames() and getDuration() methods of
// AnimationDrawable but I am just hardcoding it for simplicity
int duration=2000;
// This is to keep track of the animations which are in an array, I use this variable in run()
order=1;
for(int i=0;i<5;i++){
//play the first animation
if(i==0){
view.setBackground(animation[0]);
animation[i].stop();
animation[i].start();
}
else{
//Now set up the next animation to play after 2000ms, the next after 4000ms, etc.
//You are supposed to use a handler if you want to change the view in the main thread.
//it is very easy: Handler handler=new Handler():
handler.postDelayed(new Runnable(){
#Override
public void run() {
view.setBackground(animation[order]);
animation[order].stop();
animation[order].start();
order++;
}
}, duration);
//dtx12 suggestion of multiplying by i is probably smarter
duration+=2000;
}
}
}
So there you have it. Not the most elegant display of coding, but it gets the basic job done. You can't use 'i' in the run() method because it is an anonymous inner class, and if you assign the value of 'i' to another variable in the loop, it will be '4', by the time the other threads execute. So, I just did some counting inside of run to make sure everything fired in order.

Related

I can't get infinite nested loop within onCreate() method

I'm new to Android Studio programming and I'd like to know what is the substitute for code below..
I'm trying to iterate an infinite loop that has a nested one and it seem to not work. The application still crushes when it comes to that loop.
I also tried to use non-infinite loop without nesting another inside of it.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
while (state) {
int i = 0;
while (i < person[i].length) {
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
someTextView.setText(person[i].getName());
}
});
}
i++;
}
}
This Logcat below just represents the nested while loop without
the onClickListener unlike shown in code.
2019-06-01 11:59:10.695 19529-19529/com.example.app2 I/Timeline: Timeline: Activity_launch_request id:com.example.app2 time:120814793
2019-06-01 11:59:10.771 19529-19543/com.example.app2 I/art: Enter while loop.
2019-06-01 11:59:10.789 19529-19543/com.example.app2 I/art: Enter while loop.
After entering while loop all I'm getting is a black screen on my device.
How do I use these loops within onCreate() method?
I have a few questions. If you want a infinite loop why not just:
while(true)
{
//Execute some code
}
My next question is why are you trying to do this? The reason why you are getting a black screen is cause you are stuck in an infinite loop. The code for the nested loop gets executed but because you are incrementing i outside the nested loop you will always return true, i will always be less than the length of person[]. There is nothing to render because a result is never reached. If you are looking to add onClickListeners to multiple objects the far better approach is to use a Recycler view with cards, and to assign the OnclickListeners in the Recycler adapter.
Assuming your goal is to do something repeatedly, e.g. every 1 second, you could use postDelayed(...)
// Java
Handler handler = new Handler();
int delay = 1000; // milliseconds
handler.postDelayed(new Runnable() {
public void run() {
// do something
handler.postDelayed(this, delay);
}
}, delay);
Source: How to run a method every X seconds

Sleep function not lagging in front of AI's turn

I need to add an artificial pause before my AI plays its turn on the tic tac toe board. However, when I try to use something like Thread.sleep, in the location where I have the comment, the entire onClick function lags. What I mean by lagging is that the ((Button) v).setText("0") does not set the button text to 0 for the amount of time I use the sleep function and the moment that the timer is up, everything happens immediately. It is like the lag happens in the beginning of the function rather than the middle where the comment is. Is there anyway to address this problem or explain why the sleep function isn't lagging where it is suppose to be?
public void onClick(View v) {
if(!((Button) v).getText().toString().equals("")){
return;
}
((Button) v).setText("0");
turn();
// Need a pause before tictacAI does anything
tictacAI("0", "X").setText("X");
turn();
if(won){
resetBoard();
won = false;
}
}
When using Thread.sleep in onClick, you are actually blocking the entire UI Thread, This will prevent Android Framework redraw your view, so your change to View's property will not be displayed.
In order to delay the execution, you should use View.postDelayed, which will post your action to a message queue and execute it later.
public void onClick(View v) {
if(!((Button) v).getText().toString().equals("")){
return;
}
((Button) v).setText("0");
turn();
// Need a pause before tictacAI does anything
v.postDelayed(new Runnable() {
#Override
public void run() {
tictacAI("0", "X").setText("X");
turn();
if(won){
resetBoard();
won = false;
}
}
}, 1000L); //wait 1 second
}
If you are using Java 1.8, you can use lambda instead of anonymous class.
Another note:
this Runnable will not be garbage collected until executed, so a long interval may cause your entire view leaked, which is very bad. Consider using View.removeCallbacks when this callback is not needed anymore(like activity's onDestroy, for example)

How do I loop a piece of code in my main activity?

I am using Android Studio and I wanted to loop this every half a second
"Random rand = new Random();
int value = rand.nextInt(10);"
So anyway thanks for your time and if you can help that would be great. :)
Sincerely,
Igor
EDIT
Thanks everyone for the kind and helpful answers. I will choose the best answer soon after I try each one out. (Not with my computer right now) But once again, thank you all.
Edit
For anyone having a similar problem I got it to work. Here is the final code.
package sarju7.click;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import java.util.Random;
public class MainActivity extends ActionBarActivity {
Random rand = new Random();
Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Runnable r = new Runnable() {
public void run() {
int value = rand.nextInt(10);
TextView t1 = (TextView) findViewById(R.id.clicker);
t1.setText(Integer.toString(value));
handler.postDelayed(this, 400);
}
};
handler.postDelayed(r, 400);
}
}
Once again thanks everyone. You guys are the best. I love all of stack overflow!
Use postDelayed(). For example, this activity shows a Toast every five seconds:
/***
Copyright (c) 2012 CommonsWare, LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
From _The Busy Coder's Guide to Android Development_
http://commonsware.com/Android
*/
package com.commonsware.android.post;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class PostDelayedDemo extends Activity implements Runnable {
private static final int PERIOD=5000;
private View root=null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
root=findViewById(android.R.id.content);
}
#Override
public void onResume() {
super.onResume();
run();
}
#Override
public void onPause() {
root.removeCallbacks(this);
super.onPause();
}
#Override
public void run() {
Toast.makeText(PostDelayedDemo.this, "Who-hoo!", Toast.LENGTH_SHORT)
.show();
root.postDelayed(this, PERIOD);
}
}
Your run() method of your Runnable is where you do the work and schedule the Runnable to run again after your desired delay period. Just call removeCallbacks() to end the looping. You can call postDelayed() on any widget; in my case, I am using the framework-supplied FrameLayout known as android.R.id.content, as this activity has no other UI.
Random rand = new Random();
Handler handler = new Handler()
Runnable r=new Runnable() {
public void run() {
int value = rand.nextInt(10);
handler.postDelayed(this, 500);
}
};
handler.postDelayed(r, 500);
Use this
create this in onCreate
Random rand = new Random();
handler=new Handler();
handler.postDelayed(myRunnable, 100);
declare it outside onCreate
myRunnable=new Runnable() {
#Override
public void run() {
int value = rand.nextInt(10);
handler.postDelayed(this, 100);
}
}
While all of the answers here are good, they do not necessarily address why you want this or what you will do with the result. You can do this anywhere in your code, but you are probably asking because it will block the UI thread if it's run there and you probably don't want that.
You need to run this in the background on a different thread or service so the user can interact while the loop is running. That is the basic answer - run this loop on another thread besides the MAIN or UI thread. (There are a lot of answers here that address that.)
If you want a random number to display on the screen every half second, then a lot of these options are fine except they don't explain that if you run them in a different thread, then you need to create the class in your Activity and then use the runOnUiThread thread method to update your view classes (otherwise you will get errors).
If you want to use it as the start to doing further background processing, you should consider a Service where you can expand the functionality of your loop and whatever other tasks it may need to perform while the UI thread is running. For example, if you are needing random numbers to select images that are displayed on the screen, you may want to run this in a service that provides images to the Activity.
Hope that helps.

Android thread confusion

I have written a function to create a splash screen with a 5 second timeout for my app.
The code works fine, but when the timeout reaches zero and I want to redirect to my main activity, the app crashes with the following error:
Only the original thread that created a view hierarchy can touch its views.
So I looked around a bit and someone suggested nesting this inside my function. It seems like a good Idea, but now methods like sleep / stop won't work.
My code is below, I can provide more / explain more in details if it isn't clear enough just let me know. Thanks for the help.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showSplashScreen();
}
protected boolean _active = true;
protected int _splashTime = 5000; // Splash screen is 5 seconds
public void showSplashScreen() {
setContentView(R.layout.splash_layout);
// Thread splashThread = new Thread() {
runOnUiThread(new Runnable() {
#Override
public void run() {
try {
int waited = 0;
while (_active && (waited < _splashTime)) {
Thread.sleep(100);
if (_active) {
waited += 100;
}
}
} catch (InterruptedException e) {
// do nothing
} finally {
showApplication();
}
}
});
}
Probably not what you want to hear, but you should never put a splash screen on your mobile app. With the exception of games, when people use a mobile app they want to get in, do what ever it is they need to do, and get out. If you make that process take longer, people are just going to get frustrated with you app. You should probably reconsider just not using a splash screen.
This will perform sleep on the UI thread. That's never a good idea.
Why not something like this?
new Handler().postDelayed(new Runnable() {
public void run() {
// start application ...
}
}, _splashTime);
But this answer has a good point. Displaying a splash screen for 5 seconds can be very annoying.
I believe you want AsyncTask for this. The method called on completion of the task will be called on your UI thread, making modifying UI elements much easier.
Use a Handler to post an event to the UI thread that will remove the splash.
Code should be something like...
splash.show()
new Handler().postDelayed(
new Runnable() {
void run() {
splash.remove();
},
delayTime);
I suggest you to make new activity for your spalsh screen, show it in a regular way (with startActivityForResult) and place in it such code (in it, not in your main activity):
new Handler().postDelayed( new Runnable()
{
public void run()
{ finish(); }
}, 5000 );
Also you can handle in this new activity click events for giving opportunity to user to close it faster, tapping on it.

Setting images with a timer

I am trying to set images with a 600ms delay using a countdown timer as follows:
protected void onCreate(Bundle savedInstanceState) {
int order[3];
index=0;
image1.setOnClickListener(this);
#Override
public void onClick(View arg0)
{
order={2,1,3};
do_switches();
}
private void do_switches()
{
new CountdownTimer(3*600, 600) {
public void onTick(long millisUntilFinished) {
switch(order[index]){
case 1:
image1.setImageResource(R.drawable.one);
break;
case 2:
image2.setImageResource(R.drawable.two);
break;
case 3:
image3.setImageResource(R.drawable.three);
break;
}
index++;
}
public void onFinish() {
}
}.start();
}
}
This is an example but in my actual code, I use larger arrays (about 50 in size). My problem is, the countdown timer does not always go all the way to the last spot in the array. Sometimes works but other times it stops before reaching the last point of my array. However if I increase the interval time to be 2000ms it always works. Its definitely some processing going on that delays this but I don't expect a countdown to not work just because I have reduced the interval. Can someone tell me what I can do to make sure the countdown timer does not stop before going through my whole array or is there a better way to do this.
I also tried using a timer with scheduleAtFixedRate but I got an error saying I cannot touch views in a thread because they were not constructed in the thread
Please help
Thanks
I'm sure there are other details involved and this may not cure everything, but for the last bit ("...cannot touch views in a thread..."), you need to pass a Runnable to Activity.runOnUiThread. In that Runnable, do your setting of image resources and anything else that will affect the screen.

Categories