image - Android AnimationDrawable et savoir quand l'animation se termine




studio xml (11)

Je veux faire une animation avec plusieurs fichiers image, et pour cela AnimationDrawable fonctionne très bien. Cependant, j'ai besoin de savoir quand l'animation commence et quand elle se termine (c'est-à-dire ajouter un écouteur comme Animation.AnimationListener). Après avoir cherché des réponses, j'ai un mauvais pressentiment que AnimationDrawable ne supporte pas les auditeurs.

Est-ce que quelqu'un sait comment créer une animation d'image image par image avec un écouteur sur Android?


Answers

Après quelques lectures, j'ai trouvé cette solution. Je suis toujours surpris qu'il n'y ait pas d'auditeur dans l'objet AnimationDrawable , mais je ne voulais pas renvoyer les rappels en avant et en arrière, alors j'ai créé une classe abstraite qui déclenche une méthode onAnimationFinish() . J'espère que ça aidera quelqu'un.

La classe de dessin animé d'animation personnalisée:

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

Pour utiliser cette classe:

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

Si vous souhaitez implémenter votre animation dans adaptateur, utilisez la prochaine classe publique. CustomAnimationDrawable étend 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();
}

}

et implémentation dans 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();
        }
    });
}

Je préfère ne pas opter pour une solution temporelle, car il me semble que ce n’est pas assez fiable.

J'adore la solution de Ruslan Yanchyshyn: https://.com/a/12314579/72437

Cependant, si vous remarquez le code avec soin, nous recevrons un rappel de fin d'animation, pendant le début de l'animation de la dernière image, pas la fin de l'animation.

Je propose une autre solution, en utilisant un mannequin pouvant être dessiné en animation .

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

C'est ainsi que nous pouvons utiliser la classe ci-dessus.

    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://.com/questions/14297003/animating-all-items-in-animation-list
    animationDrawable2.setEnterFadeDuration(this.configMediumAnimTime);
    animationDrawable2.setExitFadeDuration(this.configMediumAnimTime);

    animationDrawable2.start();

J'aime aussi la réponse de Ruslan, mais j'ai dû apporter quelques modifications pour que cela fasse ce que je voulais.

Dans mon code, je me suis débarrassé du drapeau de Ruslan, et j'ai également utilisé le booléen renvoyé par super.selectDrawable() .

Voici mon code:

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;

    }

}

Et voici un exemple de la façon de le mettre en œuvre ...

public class MyFragment extends Fragment implements AnimationDrawableWithCallback.IAnimationFinishListener {

    @Override
    public void onAnimationChanged(int index, boolean finished) {

        // Do whatever you need here

    }

}

Si vous voulez seulement savoir quand le premier cycle d'animation est terminé, vous pouvez définir un indicateur booléen dans votre fragment / activité.


Je ne connais pas toutes ces autres solutions, mais c'est celle qui se rapproche le plus du simple ajout d'un auditeur à la classe 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;
        }
    }

J'ai eu le même problème quand j'ai dû implémenter un clic de bouton après que l'animation se soit arrêtée. J'ai vérifié l'image courante et la dernière image de l'animation pouvant être dessinée pour savoir quand une animation est arrêtée. Notez que ce n'est pas un écouteur, mais juste un moyen de le savoir s'est arrêté.

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

La fin de l'animation peut être facilement suivie en remplaçant la méthode selectDrawable dans la classe AnimationDrawable. Le code complet est le suivant:

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

J'ai utilisé une fonction récursive qui vérifie si l'image actuelle est la dernière image à chaque foisBetweenChecks millisecondes.

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

Je suppose que votre code ne fonctionne pas, car vous essayez de modifier une vue à partir d'un thread non-interface utilisateur. Essayez d'appeler runOnUiThread (Runnable) à partir de votre activité. Je l'ai utilisé pour faire disparaître un menu après une animation pour ce menu. Ce code fonctionne pour moi:

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

J'avais besoin de savoir à quel moment AnimationDrawable se termine, sans avoir à sous-classer AnimationDrawable car je dois définir la liste d'animation en XML. J'ai écrit ce cours et l'ai testé sur Gingerbread et ICS. Il peut facilement être étendu pour donner un rappel sur chaque image.

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

Voici comment l'utiliser.

AnimationDrawable countdownAnimation = (AnimationDrawable) mStartButton.getBackground();
countdownAnimation.setCallback(new AnimationDrawableCallback(countdownAnimation, mStartButton) {
    @Override
    public void onAnimationComplete() {
        // TODO Do something.
    }
});
countdownAnimation.start();

SDK Android rév. 17 prend en charge l'accélération de machine virtuelle à l'aide des technologies de virtualisation AMD et Intel.

Cette fonctionnalité peut améliorer beaucoup les performances de l'émulateur!

Consultez la section suivante de la documentation de l'émulateur Android pour plus de détails: Configuration de l'accélération de machine virtuelle

N'oubliez pas d'installer le pilote approprié à votre système d'exploitation:

Après avoir installé les pilotes et téléchargé une image système Android X86 (comme décrit dans la documentation), vous devriez pouvoir créer un nouveau fichier AVD à l'aide de l'image x86:

Par exemple:

  • Cible: Image système Intel Atom x86 - API de niveau 10
  • CPU / ABI: Intel Atom (x86)






android