web-dev-qa-db-fra.com

Image en rotation. Liste d'animation ou rotation animée? (Android)

Je veux créer une image de progrès en rotation et me demander quelle est la meilleure façon de procéder. Je peux le faire fonctionner avec une liste d'animation avec par exemple 12 images changeant toutes les 100ms. Cela fonctionne bien, mais il est assez fastidieux de créer 12 images ou pour chaque taille et résolution:

<animation-list xmlns:Android="http://schemas.Android.com/apk/res/Android" Android:oneshot="false">
<item Android:drawable="@drawable/ic_loading_grey_on_black_01" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_02" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_03" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_04" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_05" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_06" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_07" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_08" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_09" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_10" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_11" Android:duration="100" />
<item Android:drawable="@drawable/ic_loading_grey_on_black_12" Android:duration="100" />

Je suppose qu'une solution plus simple consiste à utiliser une image par résolution, mais plutôt à la faire pivoter pour chaque image. Dans les ressources de la plate-forme (Android-sdk-windows/plateformes ...), j'ai trouvé quelque chose appelé Animated-Rotation dans le fichier drawable/search_spinner.xml, mais si je copie le code, une erreur du compilateur se plaint d'Android: framesCount et Android: frameDuration (Google API 2.2 dans Eclipse):

<animated-rotate xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:drawable="@drawable/spinner_black_20"
Android:pivotX="50%"
Android:pivotY="50%"
Android:framesCount="12"
Android:frameDuration="100" />

J'ai également essayé d'utiliser une animation de rotation répétée (à utiliser dans le dossier de ressources anim), mais je préfère en réalité l'aspect de la version de liste d'animation.

Quelle est la méthode recommandée pour résoudre ce problème?

28
Gunnar Lium

Rotate drawable suggéré par Praveen ne vous donnera pas le contrôle du nombre d'images. Supposons que vous souhaitiez implémenter un chargeur personnalisé composé de 8 sections:

gif_icon

En utilisant l'approche animation-list, vous devez créer 8 images pivotées manuellement de 45*frameNumber degrés. Alternativement, vous pouvez utiliser la 1ère image et y définir l'animation de rotation:

progress_icon

Fichier res/anim/progress_anim.xml:

<?xml version="1.0" encoding="utf-8"?>
<rotate
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:fromDegrees="0"
    Android:toDegrees="360"
    Android:pivotX="50%"
    Android:pivotY="50%"
    Android:repeatCount="infinite" />

Fichier MainActivity.Java

Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(1000);
imageView.startAnimation(a);

Cela vous donnera une animation fluide au lieu de 8 étapes. Pour résoudre ce problème, nous devons implémenter un interpolateur personnalisé:

a.setInterpolator(new Interpolator() {
    private final int frameCount = 8;

    @Override
    public float getInterpolation(float input) {
        return (float)Math.floor(input*frameCount)/frameCount;
    }
});

Aussi, vous pouvez créer un widget personnalisé:

Fichier res/values/attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ProgressView">
        <attr name="frameCount" format="integer"/>
        <attr name="duration" format="integer" />
    </declare-styleable>
</resources>

Fichier ProgressView.Java:

public class ProgressView extends ImageView {

    public ProgressView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setAnimation(attrs);
    }

    public ProgressView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setAnimation(attrs);
    }

    public ProgressView(Context context) {
        super(context);
    }

    private void setAnimation(AttributeSet attrs) {
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressView);
        int frameCount = a.getInt(R.styleable.ProgressView_frameCount, 12);  
        int duration = a.getInt(R.styleable.ProgressView_duration, 1000);
        a.recycle();

        setAnimation(frameCount, duration);
    }

    public void setAnimation(final int frameCount, final int duration) {
        Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
        a.setDuration(duration);
        a.setInterpolator(new Interpolator() {

            @Override
            public float getInterpolation(float input) {
                return (float)Math.floor(input*frameCount)/frameCount;
            }
        });
        startAnimation(a);
    }
}

Fichier activity_main.xml:

<com.example.widget.ProgressView
    Android:layout_width="wrap_content"
    Android:layout_height="wrap_content"
    Android:src="@drawable/ic_progress" 
    app:frameCount="8"
    app:duration="1000"/>

Fichier res/anim/progress_anim.xml: répertorié ci-dessus

60
vokilam

Vous devez créer un fichier XML dessinable comme ci-dessous:

Code:

<animated-rotate xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:pivotX="50%" Android:pivotY="50%" Android:fromDegrees="0"
Android:toDegrees="360" Android:drawable="@drawable/imagefile_to_rotate" />
13
Praveen

J'ai trouvé que la réponse de vokilam était la meilleure pour créer une animation de Nice pas à pas/décalée. Je suis allé chercher sa dernière suggestion et j'ai créé un widget personnalisé. Le seul problème que j'ai rencontré était que la visibilité ne fonctionnait pas car elle était animée et donc toujours visible ...

J'ai ajusté son code (ProgressView.Java que j'ai renommé StaggeredProgress.Java) comme ceci:

public class StaggeredProgress extends ImageView {

private Animation staggered;

public StaggeredProgress(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setAnimation(attrs);
}

public StaggeredProgress(Context context, AttributeSet attrs) {
    super(context, attrs);
    setAnimation(attrs);
}

public StaggeredProgress(Context context) {
    super(context);
}

private void setAnimation(AttributeSet attrs) {
    TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StaggeredProgress);
    int frameCount = a.getInt(R.styleable.StaggeredProgress_frameCount, 12);  
    int duration = a.getInt(R.styleable.StaggeredProgress_duration, 1000);
    a.recycle();

    setAnimation(frameCount, duration);
}

public void setAnimation(final int frameCount, final int duration) {
    Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
    a.setDuration(duration);
    a.setInterpolator(new Interpolator() {

        @Override
        public float getInterpolation(float input) {
            return (float)Math.floor(input*frameCount)/frameCount;
        }
    });
    staggered = a;
    //startAnimation(a);
}

@Override
public void setVisibility(int visibility) {
    super.setVisibility(visibility);
    if( visibility == View.VISIBLE )
        startAnimation(staggered);
    else
        clearAnimation();

}


}

De cette façon, définir la visibilité de la vue démarre et arrête l'animation selon les besoins ... Merci encore à vokilam!

6
user1504495

voir les exemples ici http://developer.Android.com/resources/samples/ApiDemos/src/com/example/Android/apis/view/index.html

en particulier: Barre de progression

  1. Incrémentiel Présente des indicateurs de progression rotatifs, grands et petits, qui peuvent être incrémentés ou décrémentés. 
  2. Smooth Présente des indicateurs de progression, grands et petits, à rotation continue, utilisés pour indiquer un message générique "occupé". 
  3. Dialogs Présente ProgressDialog, une boîte de dialogue contextuelle qui héberge une barre de progression. Cet exemple illustre à la fois des indicateurs de progrès déterminés et indéterminés. 
  4. Dans la barre de titre. Montre un écran d'activité avec un indicateur de progression chargé en définissant la fonction d'indicateur de progression de WindowPolicy. 
2
mishkin

Merci @vokilam. Cette solution similaire (une vue personnalisée à rotation automatique) utilise <animation-list> de manière dynamique dans son implémentation:

public class FramesAnimatorView extends AppCompatImageView {
    private int framesCount;
    private int duration;
    private Bitmap frameBitmap;

    public FramesAnimatorView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    public FramesAnimatorView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public FramesAnimatorView(Context context) { super(context); }

    private void init(Context context, AttributeSet attrs) {
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FramesAnimatorView);
        framesCount = typedArray.getInt(R.styleable.FramesAnimatorView_framesCount, 12);
        duration = typedArray.getInt(R.styleable.FramesAnimatorView_duration, 1200);
        typedArray.recycle();

        // Method 1: Use <rotate> as Animation (RotateAnimation) and startAnimation() (Rotate view itself).
        //method1(framesCount, duration);

        // Method 2: Use <rotate> as Drawable (RotateDrawable) and ObjectAnimator. Usable for API 21+ (because of using RotateDrawable.setDrawable).
        //method2();

        // Method 3 (Recommended): Use <animation-list> (AnimationDrawable) dynamically.
        final int frameDuration = this.duration / framesCount;
        final AnimationDrawable animationDrawable = (AnimationDrawable) getDrawable();

        for (int i = 0; i < framesCount; i++)
            animationDrawable.addFrame(
                    new RotatedDrawable(frameBitmap, i * 360f / framesCount, getResources()),
                    frameDuration);

        animationDrawable.start();
    }

    @Override public void setImageResource(int resId) { //info();
        frameBitmap = BitmapFactory.decodeResource(getResources(), resId);
        super.setImageDrawable(new AnimationDrawable());
    }

    @Override public void setImageDrawable(@Nullable Drawable drawable) { //info();
        frameBitmap = drawableToBitmap(drawable);
        super.setImageDrawable(new AnimationDrawable());
    }

    @Override public void setImageBitmap(Bitmap bitmap) { //info();
        frameBitmap = bitmap;
        super.setImageDrawable(new AnimationDrawable());
    }

    /**
     * See <a href="https://stackoverflow.com/a/21376008/5318303">@Android-developer's answer on stackoverflow.com</a>.
     */
    private static class RotatedDrawable extends BitmapDrawable {
        private final float degrees;
        private int pivotX;
        private int pivotY;

        RotatedDrawable(Bitmap bitmap, float degrees, Resources res) {
            super(res, bitmap);
            pivotX = bitmap.getWidth() / 2;
            pivotY = bitmap.getHeight() / 2;
            this.degrees = degrees;
        }

        @Override public void draw(final Canvas canvas) {
            canvas.save();
            canvas.rotate(degrees, pivotX, pivotY);
            super.draw(canvas);
            canvas.restore();
        }
    }

    /**
     * See <a href="https://stackoverflow.com/a/10600736/5318303">@André's answer on stackoverflow.com</a>.
     */
    @NonNull private static Bitmap drawableToBitmap(Drawable drawable) {
        final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }
}

Voir Android-FramesAnimatorView sur GitHub pour obtenir le code source complet (et probablement plus mis à jour).

0
Mir-Ismaili

La solution de SACPK fonctionne définitivement. Une autre solution peut être d'utiliser <animated-rotate> comme dans la question et de supprimer les attributs Android:framesCount="12" Android:frameDuration="100" pour ceux que le compilateur se plaint. Cela fonctionne toujours même pour mon image de 8 images. 

Cependant, je n'ai pas compris comment contrôler la vitesse de l'animation :(.

0
ernazm