Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

После некоторого чтения я придумал это решение. Я все еще удивляюсь, что нет слушателя как часть объекта 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.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();
        }
    });

Ответ 3

Мне нужно было знать, когда завершится мой однозарядный 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();

Ответ 4

Окончание анимации можно легко отследить, переопределив метод 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;
    }
}

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

В моем коде я избавился от значка 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

    }

}

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

Ответ 9

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

Ответ 10

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

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

Ответ 11

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

Ответ 12

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

Я люблю решение Руслана Янчишина: fooobar.com/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 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();

Ответ 13

Вы можете использовать registerAnimationCallback чтобы проверить начало и конец анимации.

Вот код фрагмента:

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

Целью ImageExt было отключение после запуска анимации (в процессе), чтобы пользователь не спамил анимацию и не приводил к появлению сломанного/неправильного вектора.

Покадрово, вы можете запустить другой ImageView, как это.

// Animation.kt
iv1.startAnim {
    iv2.startAnim {
        // iv3, etc
    }
}

Но вышеприведенные решения выглядят некрасиво. Если у кого-то есть лучший подход, пожалуйста, прокомментируйте ниже или отредактируйте этот ответ напрямую.

Ответ 14

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

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

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