Android async task frame animation - android

To begin with I have tried a lot of ways to make a smooth animation in Android and probably my best option was to use AnimationDrawable. Everything was perfect until I got out of memory exception on older devices. The reason for that obviously is the number of frames, in my case 75. That is how I got to the point of using AsyncTask and Thread.sleep() to animate the frames. To avoid animation lag I used a Stack in which I preload the first 10 frames and then just pop the used one and push a new one until there are no more frames. Everything worked better than I expected, but the only problem is that at the end of the animation the last frame disappears and I am hitting my head whole day to understand why is that happening with no success obviously. Below is the code from the Activity in which I call the animation and the file where the animation code is.
SplashActivity.java
private void startAnimation() {
gifImageView = (LogoAnimImageView) findViewById(R.id.gifImageView);
gifImageView.setSplashActivityContext(this);
gifImageView.setBackgroundResource(R.drawable.logo_frame_0);
gifImageView.setAnimImageViewListener(new LogoAnimImageView.LogoAnimImageViewInterface() {
#Override
public void animationEnd() {
mAnimationFinished = true;
LoadNextActivity();
}
});
gifImageView.startLogoAnimation();
}
LogoAnimImageView.java
public class LogoAnimImageView extends ImageView {
public interface LogoAnimImageViewInterface {
void animationEnd();
}
final Handler mHandler = new Handler();
private Stack<Drawable> mImageStack;
private SplashActivity mSplashActivity;
private LogoAnimImageViewInterface mListener;
private int mFrameIndex;
private int[] mResources = {R.drawable.logo_frame_0,R.drawable.logo_frame_1,R.drawable.logo_frame_2,R.drawable.logo_frame_3,
R.drawable.logo_frame_4,R.drawable.logo_frame_5,R.drawable.logo_frame_6,
R.drawable.logo_frame_7,R.drawable.logo_frame_8,R.drawable.logo_frame_9,R.drawable.logo_frame_10,
R.drawable.logo_frame_11,R.drawable.logo_frame_12,R.drawable.logo_frame_13,R.drawable.logo_frame_14,
R.drawable.logo_frame_15,R.drawable.logo_frame_16,R.drawable.logo_frame_17,R.drawable.logo_frame_18,
R.drawable.logo_frame_19,R.drawable.logo_frame_20,R.drawable.logo_frame_21,R.drawable.logo_frame_22,
R.drawable.logo_frame_23,R.drawable.logo_frame_24,R.drawable.logo_frame_25,R.drawable.logo_frame_26,
R.drawable.logo_frame_27,R.drawable.logo_frame_28,R.drawable.logo_frame_29,R.drawable.logo_frame_30,
R.drawable.logo_frame_31,R.drawable.logo_frame_32,R.drawable.logo_frame_33,R.drawable.logo_frame_34,
R.drawable.logo_frame_35,R.drawable.logo_frame_36,R.drawable.logo_frame_37,R.drawable.logo_frame_38,
R.drawable.logo_frame_39,R.drawable.logo_frame_40,R.drawable.logo_frame_41,R.drawable.logo_frame_42,
R.drawable.logo_frame_43,R.drawable.logo_frame_44,R.drawable.logo_frame_45,R.drawable.logo_frame_46,
R.drawable.logo_frame_47,R.drawable.logo_frame_48,R.drawable.logo_frame_49,R.drawable.logo_frame_50,
R.drawable.logo_frame_51,R.drawable.logo_frame_52,R.drawable.logo_frame_53,R.drawable.logo_frame_54,
R.drawable.logo_frame_55,R.drawable.logo_frame_56,R.drawable.logo_frame_57,R.drawable.logo_frame_58,
R.drawable.logo_frame_59,R.drawable.logo_frame_60,R.drawable.logo_frame_61,R.drawable.logo_frame_62,
R.drawable.logo_frame_63,R.drawable.logo_frame_64,R.drawable.logo_frame_65,R.drawable.logo_frame_66,
R.drawable.logo_frame_67,R.drawable.logo_frame_68,R.drawable.logo_frame_69,R.drawable.logo_frame_70,
R.drawable.logo_frame_71,R.drawable.logo_frame_72,R.drawable.logo_frame_73,R.drawable.logo_frame_74
};
public LogoAnimImageView(Context context) {
super(context);
}
public LogoAnimImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LogoAnimImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void startLogoAnimation() {
mFrameIndex = 10;
mImageStack = new Stack<Drawable>();
for (int i=1;i<=mFrameIndex;i++) {
Drawable drawable = getDrawable(mResources[i]);
mImageStack.push(drawable);
}
mFrameIndex++;
mSplashActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
new LogoAnimOperation().execute((Object)null);
}
});
}
public void setSplashActivityContext(SplashActivity splashActivity) {
this.mSplashActivity = splashActivity;
}
public void setAnimImageViewListener(LogoAnimImageViewInterface listener) {
this.mListener = listener;
}
private Drawable getDrawable(int id) {
Drawable drawable;
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){
drawable = mSplashActivity.getDrawable(id);
} else {
drawable = mSplashActivity.getResources().getDrawable(id);
}
return drawable;
}
private class LogoAnimOperation extends AsyncTask<Object,Void,String> {
#Override
protected String doInBackground(Object... params) {
int number=1;
while (mImageStack.size() > 1) {
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
final Drawable drawable = mImageStack.pop();
mSplashActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
LogoAnimImageView.this.setBackground(drawable);
}
else {
LogoAnimImageView.this.setBackgroundDrawable(drawable);
}
if (mFrameIndex < mResources.length) {
Drawable newDrawable = getDrawable(mResources[mFrameIndex]);
mImageStack.push(newDrawable);
mFrameIndex++;
}
}
});
}
return "";
}
#Override
protected void onPostExecute(String s) {
mSplashActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Drawable drawable = getDrawable(R.drawable.logo_frame_74);
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
LogoAnimImageView.this.setBackground(drawable);
}
else {
LogoAnimImageView.this.setBackgroundDrawable(drawable);
}
}
});
mListener.animationEnd();
super.onPostExecute(s);
}
}
}

...but the only problem is that at the end of the animation the last
frame disappears and I am hitting my head whole day to understand why
is that happening with no success obviously.
The problem may lie in your AsyncTask's onPostExecute(String):
#Override
protected void onPostExecute(String s) {
mSplashActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Drawable drawable = getDrawable(R.drawable.logo_frame_74);
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
LogoAnimImageView.this.setBackground(drawable);
} else {
LogoAnimImageView.this.setBackgroundDrawable(drawable);
}
}
});
mListener.animationEnd();
super.onPostExecute(s);
}
onPostExecute(String) will always be called on the UI thread. So, mSplashActivity.runOnUiThread(....) is redundant.
By using runOnUiThread(Runnable), you are posting to the UI thread's event queue. So, the runnable is executed when its turn comes up. However, the code after the mSplashActivity.runOnUiThread(....) call may get executed before the runnable. So, mListener.animationEnd() may be getting called before your LogoAnimImageView has a chance to display R.drawable.logo_frame_74.
But, this should not happen in your case. If runOnUiThread(Runnable) is called from the UI thread (which, it is), the Runnable is not posted to the event queue, and executed immediately instead.
I suspect that the real issue here is that there isn't any delay between the last frame of your animation (R.drawable.logo_frame_74), and launch of next activity. Perhaps you could comment out the call to mListener.animationEnd(), to check whether the animation ends at the last or second-last frame.
Although this is an interesting approach, and one I haven't seen before, I have to say that you are meddling with more threads than you need to. If you're trying to load Drawables as and when they are needed, there is a simpler way:
public class LogoAnimImageView extends ImageView {
....
....
// flag to indicate whether `mNextFrameDrawable` should continue loading the next frame
private boolean mStopAnimating;
// loads the next frame, and calls back to activity when done
private Runnable mNextFrameRunnable = new Runnable() {
#Override
public void run() {
if (!mStopAnimating) {
if (isFinishedAnimating() && mListener != null) {
mListener.animationEnd();
} else { // Load next frame
setViewBg(getNextFrameDrawable());
// Will load the next frame in 40 ms
postDelayed(this, 40L);
}
}
}
};
// This method can be set `public static` and placed in a separate `Utils` class
private void setViewBg(Drawable d) {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
setBackground(drawable);
} else {
setBackgroundDrawable(drawable);
}
}
private Boolean isFinishedAnimating() {
return mFrameIndex >= mResources.length;
}
// returns the next frame's drawable and increments the `mFrameIndex` pointer
private Drawable getNextFrameDrawable() {
return getDrawable(mResources[mFrameIndex++]);
}
// start animating
public void startLogoAnimation() {
mFrameIndex = 0;
mStopAnimating = false;
post(mNextFrameRunnable);
}
// stop animating
public void stopLogoAnimation() {
mStopAnimating = true;
removeCallbacks(mNextFrameRunnable);
}
....
....
}
AsyncTask is neither needed, nor designed to handle such scenarios.

Related

Unable to change visibility of view other than onCreate method

I am unable to change the view visibility inside other function rather than onCreate method. Its working only at time of onCreate is called.
public class CameraXActivity extends AppCompatActivity {
...
public Group fipGroup;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camerax_layout); // Created xml using constraintLayout.
//intial setup
fipGroup = (Group)findViewById(R.id.fip_group);
startCamera();
//hideFipGroup(); <<--- This is working
}
private void hideFipGroup() {
Log.d(TAG, "=== hideFipGroup ===");
fipGroup.setVisibility(View.INVISIBLE);
}
private void startCamera() {
CameraX.unbindAll();
preview = setPreview();
imageAnalysis = setImageAnalysis();
//bind to lifecycle:
CameraX.bindToLifecycle(this, preview , imageAnalysis);
preview.enableTorch(true);
}
private ImageAnalysis setImageAnalysis() {
hideFipGroup() // This is working
imageAnalysis.setAnalyzer(
new ImageAnalysis.Analyzer() {
#Override
public void analyze(ImageProxy image, int rotationDegrees) {
hideFipGroup() // Exactly, Failed at this place.
}
}
)
}
}
Edit Update:
It's failing to update on the analyze method of imageAnalysis. Just to test, called toast message which is showing on the UI. But am not able to control the UI.
private void raiseToast(String msg) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
Toast toast = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 500);
toast.show();
}
});
}
The problem is with Group visibility on constraint Layout.
private void setGroupVisiblity(Group group, int visibility) {
group.setVisibility(visibility);
group.requestLayout();
}
or manually turn off each views in the group:
private void setGroupVisibility(ConstraintLayout layout, Group group, Integer visibility) {
int[] refIds = group.getReferencedIds();
for (int id : refIds) {
layout.findViewById(id).setVisibility(visibility);
}
}
or upgrade to ConstraintLayout version 2.0.0 beta 6
source: Can't set visibility on constraint group
Please try to view.post or runOnUIThread method.
so like this.
private ImageAnalysis setImageAnalysis() {
hideFipGroup() // This is working
imageAnalysis.setAnalyzer(
new ImageAnalysis.Analyzer() {
#Override
public void analyze(ImageProxy image, int rotationDegrees) {
fipGroup.post(new Runnable() {
#Override
public void run() {
hideFipGroup(); // Exactly, Failed at this place.
}
});
}
}
)
}
Or you can simply call view.post() method in hideFipGroup() method
private void hideFipGroup() {
Log.d(TAG, "=== hideFipGroup ===");
fipGroup.post(new Runnable() {
#Override
public void run() {
fipGroup.setVisibility(View.INVISIBLE);
}
});
}
you should check the function you called. Remember that you can only update the UI in the main thread

Android Change view content while invisible

I have two layouts, which have views inside that have changeable content and I switch between them by making them VISIBLE and INVISIBLE
Contents of view depends on response of REST service and I have to change content of specified view when response is got.
But REST service runs on background which doesn't wait for the layout which is VISIBLE.
I am applying changes to related views within Runnable called in runOnUIThread from background Thread
It looks OK so far, but is it OK to make changes on INVISIBLE layout's child View?
Sample:
when HTTP 200 is returned from server:
public void success(retrofit.client.Response response, retrofit.client.Response ignore) {
String out = new String();
TypedInput body = response.getBody();
try {
out = getString(response);
runOnUiThread(new Runnable() {
#Override
public void run() {
txtStuCounter.setText(out);
} catch (Exception e) {
e.printStackTrace();
}
}
and TimerTask switches between views with calling function below depending on time:
private void showHideCourse(boolean show) {
if (show) {
if (isMultiple == false) {
layoutCourse.setVisibility(View.VISIBLE);
layoutMCourse.setVisibility(View.INVISIBLE);
txtStuCounter.setVisibility(View.VISIBLE);
} else {
layoutMCourse.setVisibility(View.VISIBLE);
layoutCourse.setVisibility(View.INVISIBLE);
txtStuCounter.setVisibility(View.INVISIBLE);
}
if (!noSchedule) {
//DersProgrami yururlukte
layoutSchedule.setVisibility(View.INVISIBLE);
isSchedule = false;
} else {
//Toplanti salonu videosu
logoView.stopPlayback();
wasVideoPlaying = false;
logoView.setVisibility(View.INVISIBLE);
}
}
}
use this one in xml layout
android:visibility="gone"
or use this inside your class
object.setVisibility(View.GONE);
I think Loaders are the best solution to your problem to load data in backGround
and update UI
See Example:
private LoaderManager.LoaderCallbacks<Cursor> favouriteLoaderCallbacks = new LoaderManager.LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new AsyncTaskLoader<Cursor>(getContext()) {
#Override
protected void onStartLoading() {
forceLoad();
}
#Override
public Cursor loadInBackground() {
// do your back ground work here ...
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor mCursor) {
// set you visibilty Here...
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
};
then intit the loaser in onCreate Methode
getLoaderManager().initLoader(FAVOURITE_LOADER_ID, null, favouriteLoaderCallbacks);
Read More
Loaders

Android - AnimationDrawable flashing white hole while playing a custom bitmap drawable

I write the custom ImageView to display an animation with list of bitmaps. Here is my source code:
public class CustomImageView extends ImageView {
public CustomImageView(Context context) {
super(context);
}
public void startAnimation(List<BitmapDrawable> arrBitmapDelay, int[] durations) {
if (arrBitmapDelay.size() > 1 && arrBitmapDelay.size() == durations.length) {
final AnimationDrawable oAnimation = new AnimationDrawable();
oAnimation.setOneShot(false);
int i = 0;
for (BitmapDrawable oBitmapDelay : arrBitmapDelay) {
oAnimation.addFrame(oBitmapDelay, durations[i]);
i++;
}
if(getContext() instanceof Activity)
if(((Activity)getContext()).isFinishing())
return;
if(oAnimation.getNumberOfFrames()<=0) return;
setImageDrawable(oAnimation);
post(new Runnable() {
#Override
public void run() {
oAnimation.start();
}
});
}
}
}
And the result: https://goo.gl/photos/FSC5RaEE2ajfe23v6
You can see, it loop correctly time, but sometimes it flashing... Please help!
EDIT
I add code of reading list bitmap.
public void decodeAndShow() {
List<Bitmap> bitmaps = new ArrayList<>();
int[] duration = new int[20];
for (int i=0; i<20; i++) {
bitmaps.add(BitmapFactory.decodeFile(new File(getContext().getCacheDir(), "bitmapsample"+i+".png").getAbsolutePath()));
duration[i] = 100;
}
img.startAnimation(bitmaps, duration);
}
Sorry, because my project is too much complicated to copy here.
Add android:hardwareAccelerated="true" to your manifest, either for the or the . This ensures that your app uses the devices graphics card and should help with your animation.
I fixed by myself with a cheat. I don't know why it's OK.
This is my code:
public class CustomImageView extends ImageView {
private Runnable runningAnimation;
public CustomImageView(Context context) {
super(context);
}
public void startAnimation(List<BitmapDrawable> arrBitmapDelay, int[] durations) {
if(runningAnimation != null) {
this.removeCallbacks(runningAnimation);
}
if (arrBitmapDelay.size() > 1 && arrBitmapDelay.size() == durations.length) {
final AnimationDrawable oAnimation = new AnimationDrawable();
oAnimation.setOneShot(false);
int i = 0;
for (BitmapDrawable oBitmapDelay : arrBitmapDelay) {
oAnimation.addFrame(oBitmapDelay, durations[i]);
i++;
}
if(getContext() instanceof Activity)
if(((Activity)getContext()).isFinishing())
return;
if(oAnimation.getNumberOfFrames()<=0) return;
setImageDrawable(oAnimation);
runningAnimation = new Runnable() {
#Override
public void run() {
oAnimation.start();
}
}
post(runningAnimation);
}
}
}

Cannot repeat animation on Floating Action Button

Now that I can animate my FloatingActionButton, I have another issue.
When I click on the FAB, the rotation of the FAB starts and keeps going until an AsyncTask finishes its job.
Here are the parts of the code related:
#OnClick(R.id.floating_action_button) //Butterknife feature
public void refreshAccountInfo(){
APIHandler api = new APIHandler(databaseHelper, c, getActivity());
AccountSync accountSync = new AccountSync(api);
accountSync.execute();
}
public class AccountSync extends AsyncTask<Void,Void,Account> {
APIHandler apiHandler;
ObjectAnimator animation;
public AccountSync(APIHandler api){
apiHandler = api;
}
#Override
protected void onPreExecute(){
FloatingActionButton button = ButterKnife.findById(getActivity(), R.id.floating_action_button);
PropertyValuesHolder pvhR = PropertyValuesHolder.ofFloat(View.ROTATION, -360);
animation = ObjectAnimator.ofPropertyValuesHolder(button, pvhR);
animation.setRepeatCount(Animation.INFINITE);
animation.setDuration(1500);
animation.start();
}
#Override
protected Account doInBackground(Void... params) {
Account a;
try {
a = apiHandler.getAccountInfo();
return a;
} catch (final ResponseException e) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(apiHandler.getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
});
return null;
}
}
#Override
protected void onPostExecute(Account result) {
animation.setRepeatCount(0);
if(result!=null){
a = result;
showInfo();
Toast.makeText(getActivity(),"Account synchronization done",Toast.LENGTH_SHORT).show();
}
}
}
My problem is, when I trigger the button the first time, the animation plays fine, but when I try to click more times, the AsyncTask will do its job, but the rotation animation will never play until I reload the fragment completely.
How can I fix that ?
You can for example end the animation in onPostExecute like this :
#Override
protected void onPostExecute(Account result) {
if(animation.isRunning())
animation.end();
...
}
}
That should be more efficient than setting repeatCount to 0.

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.

Categories

Resources