Android AnimationDrawable и знание, когда анимация заканчивается
Я хочу сделать анимацию с несколькими файлами изображений, и для этого AnimationDrawable работает очень хорошо. Тем не менее, мне нужно знать, когда начинается анимация и когда она заканчивается (например, добавить слушателя, как Animation.AnimationListener). После поиска ответов, у меня плохое ощущение, что AnimationDrawable не поддерживает слушателей.
Кто-нибудь знает, как создать кадровую анимацию с прослушивателем на Android?
- обрабатывать ссылку на текстовое изображение в приложении для Android
- listView динамический элемент добавления
- Сохранить bitmap в местоположении
- Android: Как использовать веб-камеру в эмуляторе?
- ArrayAdapter в android для создания простого списка
- Является ли отказ от приложения недоверчивым?
- Обмен данными между двумя приложениями
- Как изменить андроид неопределенный цвет ProgressBar?
- Оффлайн-возможности Firebase и addListenerForSingleValueEvent
- Ошибка разрешения Android 6.0
- Как я могу вернуться к родительской активности правильно?
- Подключение 2 экземпляров эмулятора в Android
- Как создать горизонтальный ListView с помощью RecyclerView?
После некоторого чтения я придумал это решение. Я все еще удивлен, что нет слушателя как часть объекта AnimationDrawable
, но я не хотел передавать обратные вызовы назад и вперед, поэтому вместо этого я создал абстрактный class, который вызывает метод onAnimationFinish()
. Я надеюсь, что это помогает кому-то.
Пользовательский 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(); }
Чтобы использовать этот 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(); } });
Мне нужно было знать, когда завершится мой однозарядный AnimationDrawable, без необходимости подclassа AnimationDrawable, так как я должен установить список анимации в XML. Я написал этот class и протестировал его на 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();
Окончание анимации можно легко отследить, переопределив метод selectDrawable в classе 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; } }
Я использовал рекурсивную функцию, которая проверяет, является ли текущий кадр последним кадром каждый раз в течение нескольких миллисекунд.
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-streamе, например, 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 гарантирует, что это выполняется только один раз.
если вы хотите внедрить свою анимацию в адаптер – следует использовать следующий открытый 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(); }
}
и реализация в адаптере 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(); }
Я не знаю обо всех этих других решениях, но это тот, который ближе всего подходит для простого добавления слушателя в class 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
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; } }
Так мы можем использовать вышеуказанный 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();
Мне также нравится ответ Руслана, но мне пришлось внести пару изменений, чтобы заставить его делать то, что мне нужно.
В моем коде я избавился от 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 } }
Если вы хотите узнать только, когда завершился первый цикл анимации, вы можете установить булевский флаг в вашем fragmentе / активности.
Я использовал следующий метод, и он действительно работает.
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);
Я надеюсь, что это решит вашу проблему.