Android AnimationDrawable и знать, когда заканчивается анимация - PullRequest
24 голосов
/ 07 февраля 2010

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

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

Ответы [ 13 ]

45 голосов
/ 10 июля 2011

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

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

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

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

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);
}
4 голосов
/ 16 февраля 2011

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

2 голосов
/ 12 февраля 2016

, если вы хотите реализовать анимацию в адаптере - следует использовать следующую открытый класс 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 Adapter

@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();
        }
    });
}
1 голос
/ 01 августа 2017

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

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

    }

}

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

0 голосов
/ 14 апреля 2019

Вы можете использовать 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
    }
}

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

0 голосов
/ 25 января 2016

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

Мне нравится решение Руслана Янчишина: 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();
0 голосов
/ 19 апреля 2015

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