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

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

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

13 ответов

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

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


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

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

public abstract class CustomAnimationDrawableNew extends AnimationDrawable {
 /** Handles the animation callback. */
 Handler mAnimationHandler;
 public CustomAnimationDrawableNew(AnimationDrawable ***********) {
 /* Add each frame to our animation drawable */
 for (int i = 0; i < ***********.getNumberOfFrames(); i++) {
 this.addFrame(***********.getFrame(i), ***********.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();
}

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

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, без подкласса 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 {@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 {@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 в классе 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-потоке, например, 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 расширяет AnimationDrawable {

/**
 * Handles the animation callback.
 */
Handler mAnimationHandler;
private OnAnimationFinish onAnimationFinish;
public void setAnimationDrawable(AnimationDrawable ***********) {
 for (int i = 0; i < ***********.getNumberOfFrames(); i++) {
 this.addFrame(***********.getFrame(i), ***********.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();
 }
 });
}


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

Я люблю решение Руслана Янчишина: qaru.site/questions/232014/...

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

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

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></item></animation-list>

AnimationDrawableWithCallback.java

import android.graphics.drawable.AnimationDrawable;
/**
 * Created by yccheok on 24/1/2016.
 */
public class AnimationDrawableWithCallback extends AnimationDrawable {
 public AnimationDrawableWithCallback(AnimationDrawable ***********) {
 /* Add each frame to our animation drawable */
 for (int i = 0; i < ***********.getNumberOfFrames(); i++) {
 this.addFrame(***********.getFrame(i), ***********.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();


Я думаю, что ваш код не работает, потому что вы пытаетесь изменить представление из не-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());


Я не знаю обо всех этих других решениях, но это тот, который ближе всего подходит для простого добавления слушателя в класс 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;
 }
 }


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

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


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

В моем коде я избавился от значка Ruslan finished, и я также использовал логическое значение, возвращаемое 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);

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

licensed under cc by-sa 3.0 with attribution.