Android的AnimationDrawable和知道什么时候animation结束
我想用几个图像文件做一个animation,为此AnimationDrawable工作得很好。 不过,我需要知道animation什么时候开始以及何时结束(即添加一个像Animation.AnimationListener一样的侦听器)。 查找答案后,我有一个不好的感觉AnimationDrawable不支持侦听器..
有谁知道如何创build一个与Android上的监听器的逐帧图像animation?
你应该知道什么时候开始,因为你是开始它的人。 而且,既然你知道持续时间(因为你指定了),你应该知道什么时候结束。
如果您不想在Java代码中硬连接持续时间,则也可以遍历所有帧( getNumberOfFrames()
)并总结每个帧的持续时间( getDuration()
)。
经过一番阅读,我想出了这个解决scheme。 我仍然感到惊讶的是没有一个监听器作为AnimationDrawable
对象的一部分,但我不想将callback传递回去,所以我创build了一个引发onAnimationFinish()
方法的抽象类。 我希望这可以帮助别人。
自定义animation可绘制的类:
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(); }
要使用这个类:
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类中覆盖selectDrawable方法,可以轻松地跟踪animation结束。 完整的代码如下:
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,因为我必须设置在XMLanimation列表。 我写了这个课,并在姜饼和ICS上进行testing。 它可以很容易地扩展到给每个框架callback。
/** * 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();
我使用了一个recursion函数来检查当前帧是否是每个时间间隔的最后一帧。
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); }
计时器是一个不好的select,因为你会卡住试图执行非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确保这只执行一次。
如果你想在适配器中实现animation – 应该使用下一个公共类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线程修改视图。 尝试从Activity中调用runOnUiThread(Runnable)。 这个菜单的animation完成后,我用它来淡出菜单。 这段代码适用于我:
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());
当我不得不在animation停止后实现button点击时,我也遇到了同样的问题。 我检查了当前帧和可绘制animation的最后一帧,以知道animation何时停止。 请注意,它不是一个监听器,而只是一个知道animation已经停止的方法。
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(); }
我不知道所有这些其他的解决scheme,但这是最接近简单地添加一个监听器到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; } }
我宁愿不去计时解决scheme,因为在我看来,这是不够可靠的。
我喜欢Ruslan Yanchyshyn的解决scheme: https ://stackoverflow.com/a/12314579/72437
但是,如果仔细注意代码,我们将在最后一帧的animation开始期间接收animation结束callback,而不是animation结束。
我提出了另一个解决scheme, 通过在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; } }
这就是我们如何利用上面的课程。
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();
我也很喜欢鲁斯兰的回答,但我必须做一些改变,才能做到我所要求的。
在我的代码中,我摆脱了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的第一个循环何时完成,那么您可以在片段/活动中设置布尔标志。
我曾经使用下面的方法,这是真的有用。
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);
我希望这会解决你的问题。