Android AnimationDrawable и знание, когда анимация заканчивается

Я хочу сделать анимацию с несколькими файлами изображений, и для этого AnimationDrawable работает очень хорошо. Тем не менее, мне нужно знать, когда начинается анимация и когда она заканчивается (например, добавить слушателя, как Animation.AnimationListener). После поиска ответов, у меня плохое ощущение, что AnimationDrawable не поддерживает слушателей.

Кто-нибудь знает, как создать кадровую анимацию с прослушивателем на Android?

Solutions Collecting From Web of "Android AnimationDrawable и знание, когда анимация заканчивается"

Вы должны знать, когда он начнется, так как вы начинаете его. И, поскольку вы знаете продолжительность (с момента ее указания), вы должны знать, когда она закончится.

Вы также можете перебирать все фреймы ( getNumberOfFrames() ) и суммировать длительность каждого из них ( getDuration() ), если вы не хотите getDuration() длительность кода Java.

После некоторого чтения я придумал это решение. Я все еще удивлен, что нет слушателя как часть объекта AnimationDrawable , но я не хотел передавать обратные вызовы назад и вперед, поэтому вместо этого я создал абстрактный класс, который вызывает метод onAnimationFinish() . Я надеюсь, что это помогает кому-то.

Пользовательский класс анимации:

 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.postDelayed(new Runnable() { 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. */ abstract void onAnimationFinish(); } 

Чтобы использовать этот класс:

  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 onAnimationFinish() { // Animation has finished... } }; // Set the views drawable to our custom drawable v.setBackgroundDrawable(cad); // Start the animation cad.start(); } }); 

Окончание анимации можно легко отследить, переопределив метод selectDrawable в классе AnimationDrawable. Полный код:

 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; } } 

Мне нужно было знать, когда завершится мой однозадачный AnimationDrawable, без подкласса AnimationDrawable, так как я должен установить список анимации в XML. Я написал этот класс и протестировал его на Gingerbread и ICS. Его можно легко расширить, чтобы обеспечить обратный вызов для каждого кадра.

 /** * 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(); } 

Вот как его использовать.

 AnimationDrawable countdownAnimation = (AnimationDrawable) mStartButton.getBackground(); countdownAnimation.setCallback(new AnimationDrawableCallback(countdownAnimation, mStartButton) { @Override public void onAnimationComplete() { // TODO Do something. } }); countdownAnimation.start(); 

Я использовал рекурсивную функцию, которая проверяет, является ли текущий кадр последним кадром каждый раз в течение нескольких миллисекунд.

 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); } 

Таймер – это плохой выбор для этого, потому что вы застрянете в попытке выполнить в не-UI-потоке, как сказал HowsItStack. Для простых задач вы можете просто использовать обработчик для вызова метода с определенным интервалом. Как это:

 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 гарантирует, что это выполняется только один раз.

Если вы хотите внедрить свою анимацию в адаптер – следует использовать следующий открытый класс 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(); } 

}

И реализация в адаптере RecycleView

 @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(); } }); } 

Я думаю, что ваш код не работает, потому что вы пытаетесь изменить представление из не-UI-Thread. Попробуйте вызвать runOnUiThread (Runnable) из вашей активности. Я использовал его для постепенного исчезновения меню после анимации для этого меню. Этот код работает для меня:

 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()); 

У меня была такая же проблема, когда мне пришлось реализовать нажатие кнопки после остановки анимации. Я проверил текущий фрейм и конечный кадр анимации, чтобы узнать, когда анимация остановлена. Обратите внимание, что это не слушатель, а просто способ узнать, что анимация остановлена.

 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(); } 

Я не знаю обо всех этих других решениях, но это тот, который ближе всего подходит для простого добавления слушателя в класс AnimationDrawable.

 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; } } 

Я предпочитаю не идти на временное решение, как мне кажется, недостаточно надежным.

Я люблю решение Руслана Янчишина: https://stackoverflow.com/a/12314579/72437

Однако, если вы внимательно заметите код, мы получим обратный вызов конца анимации во время начала анимации последнего кадра, а не конца анимации.

Я предлагаю другое решение, используя манекен, пригодный для рисования в анимации .

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; } } 

Вот как мы можем использовать вышеуказанный класс.

  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(); 

Мне также нравится ответ Руслана, но мне пришлось внести пару изменений, чтобы заставить его делать то, что мне нужно.

В моем коде я избавился от finished флага Ruslan, и я также использовал логическое значение, возвращаемое super.selectDrawable() .

Вот мой код:

 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; } } 

И вот пример того, как его реализовать …

 public class MyFragment extends Fragment implements AnimationDrawableWithCallback.IAnimationFinishListener { @Override public void onAnimationChanged(int index, boolean finished) { // Do whatever you need here } } 

Если вы хотите узнать только, когда завершился первый цикл анимации, вы можете установить логический флаг в вашем фрагменте / активности.

Я использовал следующий метод, и он действительно работает.

 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); 

Я надеюсь, что это решит вашу проблему.