481 votes

Android : Animation d'expansion/réduction

Disons que j'ai un linearLayout vertical avec :

[v1]
[v2]

Par défaut, v1 a visiblement = GONE. Je voudrais montrer v1 avec une animation d'expansion et pousser v2 vers le bas en même temps.

J'ai essayé quelque chose comme ça :

Animation a = new Animation()
{
    int initialHeight;

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final int newHeight = (int)(initialHeight * interpolatedTime);
        v.getLayoutParams().height = newHeight;
        v.requestLayout();
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        initialHeight = height;
    }

    @Override
    public boolean willChangeBounds() {
        return true;
    }
};

Mais avec cette solution, j'ai un clignotement lorsque l'animation commence. Je pense que c'est dû au fait que la v1 s'affiche en taille réelle avant que l'animation ne soit appliquée.

Avec javascript, c'est une ligne de jQuery ! Y a-t-il un moyen simple de faire cela avec Android ?

789voto

Tom Esterez Points 4923

Je vois que cette question est devenue populaire, alors je publie ma solution actuelle. L'avantage principal est que vous n'avez pas besoin de connaître la hauteur développée pour appliquer l'animation et une fois que la vue est développée, elle adapte la hauteur si le contenu change. Cela fonctionne très bien pour moi.

public static void expand(final View v) {
    int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY);
    int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    v.measure(matchParentMeasureSpec, wrapContentMeasureSpec);
    final int targetHeight = v.getMeasuredHeight();

    // Older versions of android (pre API 21) cancel animations for views with a height of 0.
    v.getLayoutParams().height = 1;
    v.setVisibility(View.VISIBLE);
    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            v.getLayoutParams().height = interpolatedTime == 1
                    ? LayoutParams.WRAP_CONTENT
                    : (int)(targetHeight * interpolatedTime);
            v.requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // Expansion speed of 1dp/ms
    a.setDuration((int)(targetHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}

public static void collapse(final View v) {
    final int initialHeight = v.getMeasuredHeight();

    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if(interpolatedTime == 1){
                v.setVisibility(View.GONE);
            }else{
                v.getLayoutParams().height = initialHeight - (int)(initialHeight * interpolatedTime);
                v.requestLayout();
            }
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };

    // Collapse speed of 1dp/ms
    a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
    v.startAnimation(a);
}

Comme l'a mentionné @Jefferson dans les commentaires, vous pouvez obtenir une animation plus lisse en modifiant la durée (et donc la vitesse) de l'animation. Actuellement, elle a été fixée à une vitesse de 1dp/ms.

14 votes

V.measure(MeasureSpec.makeMeasureSpec(LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)) ; Dans certains cas (le mien - ListView) ce décalage conduit à une valeur de targtetHeight erronée.

12 votes

Tom Esterez Cela fonctionne, mais pas de manière très fluide. Y a-t-il un travail supplémentaire à faire pour que cela fonctionne bien ?

0 votes

@acntwww Cela est dû à l'affichage de toutes les vues enfant à chaque fois. Cachez toutes les vues enfant en utilisant View.GONE juste avant de lancer l'animation et revenez à VISIBLE une fois l'animation terminée (interpolatedTime == 1). Cela vous permet également d'obtenir un bel effet de fondu.

157voto

Mr.Fu Points 71

Je suis tombé sur le même problème aujourd'hui et je suppose que la vraie solution à cette question est la suivante

<LinearLayout android:id="@+id/container"
android:animateLayoutChanges="true"
...
 />

Vous devrez définir cette propriété pour toutes les mises en page les plus hautes, qui sont impliquées dans le déplacement. Si vous définissez maintenant la visibilité d'une mise en page sur GONE, l'autre prendra l'espace au moment où celle qui disparaît le libère. Il y aura une animation par défaut qui sera une sorte de "fondu", mais je pense que vous pouvez la modifier - mais je n'ai pas encore testé cette dernière possibilité.

10 votes

Animer les changements de mise en page : developer.Android.com/training/animation/layout.html

0 votes

Il ne fonctionne pas après avoir appuyé sur le bouton retour. Avez-vous des suggestions ?

5 votes

Cela fonctionne parfaitement pour l'animation d'expansion, mais pour l'animation d'effondrement, l'animation a lieu après la réduction de la disposition parentale.

142voto

Seth Nelson Points 1588

J'ai essayé de faire ce que je crois être une animation très similaire et j'ai trouvé une solution élégante. Ce code suppose que vous allez toujours de 0->h ou h->0 (h étant la hauteur maximale). Les trois paramètres du constructeur sont view = la vue à animer (dans mon cas, une webview), targetHeight = la hauteur maximale de la vue, et down = un booléen qui spécifie la direction (true = expansion, false = effondrement).

public class DropDownAnim extends Animation {
    private final int targetHeight;
    private final View view;
    private final boolean down;

    public DropDownAnim(View view, int targetHeight, boolean down) {
        this.view = view;
        this.targetHeight = targetHeight;
        this.down = down;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        int newHeight;
        if (down) {
            newHeight = (int) (targetHeight * interpolatedTime);
        } else {
            newHeight = (int) (targetHeight * (1 - interpolatedTime));
        }
        view.getLayoutParams().height = newHeight;
        view.requestLayout();
    }

    @Override
    public void initialize(int width, int height, int parentWidth,
            int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
    }

    @Override
    public boolean willChangeBounds() {
        return true;
    }
}

5 votes

Il y a une erreur de frappe dans le code : le nom de la méthode "initalize" devrait être "initialize", sinon elle ne sera pas appelée. ;) Je recommanderais d'utiliser @Override à l'avenir pour que ce type de faute de frappe soit détecté par le compilateur.

4 votes

Je fais ce qui suit : "DropDownAnim anim = new DropDownAnim(grid_titulos_atual, GRID_HEIGHT, true) ; anim.setDuration(500) ; anim.start() ;" mais ça ne marche pas. J'ai placé des points d'arrêt sur applyTransformation mais ils ne sont jamais atteints.

6 votes

Ops, j'ai réussi à le faire fonctionner, c'est view.startAnimation(a)... Les performances ne sont pas très bonnes, mais ça marche :)

40voto

ChristophK Points 1108

Une autre solution consiste à utiliser une animation d'échelle avec les facteurs d'échelle suivants pour l'expansion :

ScaleAnimation anim = new ScaleAnimation(1, 1, 0, 1);

et pour s'effondrer :

ScaleAnimation anim = new ScaleAnimation(1, 1, 1, 0);

0 votes

Comment démarrer l'animation View.startAnimation(anim) ; ne semble pas fonctionner

0 votes

C'est exactement comme ça que je commence l'animation. D'autres animations fonctionnent-elles pour vous ?

16 votes

Cela ne repousse pas les vues situées en dessous d'elle pendant l'animation et donne l'impression d'étirer la vue animée de 0 -> h.

26voto

Tom Esterez Points 4923

Ok, je viens de trouver une solution TRES moche :

public static Animation expand(final View v, Runnable onEnd) {
    try {
        Method m = v.getClass().getDeclaredMethod("onMeasure", int.class, int.class);
        m.setAccessible(true);
        m.invoke(
            v,
            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
            MeasureSpec.makeMeasureSpec(((View)v.getParent()).getMeasuredHeight(), MeasureSpec.AT_MOST)
        );
    } catch (Exception e){
        Log.e("test", "", e);
    }
    final int initialHeight = v.getMeasuredHeight();
    Log.d("test", "initialHeight="+initialHeight);

    v.getLayoutParams().height = 0;
    v.setVisibility(View.VISIBLE);
    Animation a = new Animation()
    {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            final int newHeight = (int)(initialHeight * interpolatedTime);
            v.getLayoutParams().height = newHeight;
            v.requestLayout();
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    };
    a.setDuration(5000);
    v.startAnimation(a);
    return a;
}

N'hésitez pas à proposer une meilleure solution !

3 votes

+1, même si cela est qualifié de moche, cela fonctionne pour une vue dont on ne connaît pas encore la taille (par exemple, dans le cas où l'on ajoute une vue nouvellement créée (dont la taille est FILL_PARENT) au parent et que l'on souhaite animer ce processus, y compris l'animation de la croissance de la taille du parent).

0 votes

BTW, il semble qu'il y ait une petite erreur dans le fichier View.onMeause(widthMeasureSpec, heightMeasureSpec) donc les spécifications de largeur et de hauteur doivent être échangées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X