une - move drawable android




Android AnimationDrawable et savoir quand l'animation se termine (8)

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?


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

J'ai utilisé la méthode suivante et c'est vraiment fonctionne.

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

J'espère que cela résoudra votre problème.


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é.


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

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

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

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

Une minuterie est un mauvais choix pour cela car vous allez être bloqué en essayant de s'exécuter dans un thread autre que l'interface utilisateur, comme l'a dit HowsItStack. Pour les tâches simples, vous pouvez simplement utiliser un gestionnaire pour appeler une méthode à un certain intervalle. Comme ça:

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 assure que cela ne s'exécute qu'une fois.





android