Android: développer / réduire l'animation


449

Disons que j'ai une mise en page linéaire verticale avec:

[v1]
[v2]

Par défaut, v1 a visibily = GONE. Je voudrais montrer la v1 avec une animation développée et pousser la v2 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 clin d'œil lorsque l'animation démarre. Je pense que cela est dû à l'affichage de la v1 en taille réelle avant l'application de l'animation.

Avec javascript, c'est une ligne de jQuery! Une façon simple de le faire avec Android?

Réponses:


734

Je vois que cette question est devenue populaire, alors je poste ma solution actuelle. Le principal avantage 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. Ça marche 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 mentionné par @Jefferson dans les commentaires, vous pouvez obtenir une animation plus fluide en modifiant la durée (et donc la vitesse) de l'animation. Actuellement, il a été réglé à une vitesse de 1dp / ms


13
v.measure (MeasureSpec.makeMeasureSpec (LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec (LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); Dans certains cas (mon - ListView), cette non-concordance conduit à une valeur targtetHeight incorrecte
Johnny Doe

12
@Tom Esterez Cela fonctionne, mais pas très bien. Y a-t-il du travail supplémentaire pour le faire en douceur?
acntwww

9
@acntwww Vous pouvez obtenir une animation fluide multipliant la durée par un facteur, comme 4.a.setDuration(((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density)) * 4)
Jefferson Henrique C. Soares

10
@Alioo, import android.view.animation.Transformation;
Jomia

5
Fonctionne très bien! J'ai eu des problèmes avec la hauteur mesurée car je voulais étendre un élément dp fixe, j'ai donc changé la mesure en v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));et v.getLayoutParams().height = interpolatedTime == 1 ? targetHeight : (int)(targetHeight * interpolatedTime);cela a fonctionné pour moi!
vkislicins

140

J'essayais de faire ce que je pense ê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 = expand, false = collapsing).

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
Il y a une faute de frappe dans le code: le nom de la méthode "initalize" doit être "initialize" sinon il ne sera pas appelé. ;) Je recommanderais d'utiliser @Override à l'avenir pour que ce type de faute soit capturé par le compilateur.
Lorne Laliberte

4
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é quelques points d'arrêt sur applyTransformation mais ils ne sont jamais atteints
Paulo Cesar

5
Ops, ça a fonctionné, c'est view.startAnimation (a) ... La performance n'est pas très bonne, mais ça marche :)
Paulo Cesar

3
@IamStalker Dans cette situation, vous devez probablement initialiser avec deux variables, à partir de la hauteur et la fin de la hauteur. Puis changez en: if (down) {newHeight = (int) (((finHeight-startingHeight) * interpolatedTime) + startingHeight); } else {newHeight = (int) (((finHeight-startingHeight) * (1 - interpolatedTime)) + startingHeight); }
Seth Nelson

3
@Seth Je pense que newHeight peut simplement être (int) (((targetHeight -startingHeight) * interpolatedTime) + startingHeight), quelle que soit la direction, tant que startHeight est défini dans initialize ().
Giorgos Kylafas

138

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 dispositions les plus hautes, qui sont impliquées dans le décalage. Si vous définissez maintenant la visibilité d'une mise en page sur GONE, l'autre occupera l'espace pendant que la disparition disparaît. Il y aura une animation par défaut qui est une sorte de "disparition progressive", mais je pense que vous pouvez changer cela - mais la dernière que je n'ai pas testée, pour l'instant.


2
+1, Maintenant je recherche Speed: durée des changements animateLayoutChanges
Tushar Pandey

9
Animation des changements de mise en page: developer.android.com/training/animation/layout.html
ccpizza

Cela ne fonctionne pas après avoir appuyé sur le bouton de retour. Aucune suggestion?
Hassan Tareq

4
Cela fonctionne parfaitement pour développer une animation, mais pour la réduire, l'animation a lieu après la réduction de la disposition parent.
shine_joseph

3
@shine_joseph ouais j'utilise ceci dans une vue de recyclage et quand l'effondrement a l'air vraiment bizarre: /
AmirG

65

J'ai pris la solution de @LenaYan qui ne fonctionnait pas correctement pour moi ( car elle transformait la vue en une vue de hauteur 0 avant de réduire et / ou de développer ) et j'ai apporté quelques modifications.

Maintenant, cela fonctionne très bien , en prenant la hauteur précédente de la vue et en commençant à agrandir avec cette taille. L'effondrement est le même.

Vous pouvez simplement copier et coller le code ci-dessous:

public static void expand(final View v, int duration, int targetHeight) {

    int prevHeight  = v.getHeight();

    v.setVisibility(View.VISIBLE);
    ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (int) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(duration);
    valueAnimator.start();
}

public static void collapse(final View v, int duration, int targetHeight) {
    int prevHeight  = v.getHeight();
    ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (int) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(duration);
    valueAnimator.start();
}

Usage:

//Expanding the View
   expand(yourView, 2000, 200);

// Collapsing the View     
   collapse(yourView, 2000, 100);

Assez facile!

Merci LenaYan pour le code initial!


Bien que cela fonctionne, cela dépend des paramètres du développeur (durée de l'animation). S'il est désactivé, aucune animation ne sera affichée.
CoolMind

Oui, mais cela peut ou non être un problème. Cela dépend de votre application. Vous pouvez, par exemple, facilement faire en sorte que la durée de l'animation soit proportionnelle à la taille développée / réduite avec de simples modifications. Avoir une durée d'animation réglable vous donne un peu plus de liberté.
Geraldo Neto

Développer l'animation ne fonctionne pas. cela ressemble à une animation de repli.
Ahamadullah Saikat

39

Une alternative 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 l'effondrement:

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

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

c'est exactement comme ça que je démarre l'animation. D'autres animations fonctionnent-elles pour vous?
ChristophK

1
Je suis allé avec cette approche, fonctionne comme un charme et pas besoin de mettre en œuvre ce qui a déjà été mis en œuvre.
erbsman

15
Cela ne pousse pas les vues en dessous pendant l'animation et apparaît comme si elle étire la vue animée de 0 à> h.

5
De plus, les animations de vue fonctionnent très bien pour la mise à l'échelle: oView.animate (). ScaleY (0) pour réduire verticalement; oView.animate (). scaleY (1) pour l'ouvrir (notez que c'est uniquement disponible sdk 12 et plus).
Kirk B.15

27

La réponse de @Tom Esterez , mais mise à jour pour utiliser correctement view.measure () par Android getMeasuredHeight renvoie des valeurs erronées!

    // http://easings.net/
    Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);

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

        // Older versions of android (pre API 21) cancel animations for views with a height of 0 so use 1 instead.
        view.getLayoutParams().height = 1;
        view.setVisibility(View.VISIBLE);

        Animation animation = new Animation() {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {

               view.getLayoutParams().height = interpolatedTime == 1
                    ? ViewGroup.LayoutParams.WRAP_CONTENT
                    : (int) (targetHeight * interpolatedTime);

            view.requestLayout();
        }

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

        animation.setInterpolator(easeInOutQuart);
        animation.setDuration(computeDurationFromHeight(view));
        view.startAnimation(animation);

        return animation;
    }

    public static Animation collapse(final View view) {
        final int initialHeight = view.getMeasuredHeight();

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

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

        a.setInterpolator(easeInOutQuart);

        int durationMillis = computeDurationFromHeight(view);
        a.setDuration(durationMillis);

        view.startAnimation(a);

        return a;
    }

    private static int computeDurationFromHeight(View view) {
        // 1dp/ms * multiplier
        return (int) (view.getMeasuredHeight() / view.getContext().getResources().getDisplayMetrics().density);
    }

1
qu'est-ce que addHeight et DURATION_MULTIPLIER?
MidasLefko du

Oublié ceux-ci, addHeight est au cas où vous auriez besoin d'une hauteur supplémentaire (probablement pas) et DURATION_MODIFIER n'est qu'un modificateur de vitesse au cas où vous voudriez accélérer / ralentir les animations.
Erik B

1
Fonctionne très bien! Un petit décalage se produit lors de l'utilisation de TextView avec un seul mot sur la dernière ligne. Et pourriez-vous expliquer ce que fait le PathInterpolator ..?
yennsarah

L'easilyInOutQuart rend l'animation lente au début, puis rapide, puis lente à la fin pour une sensation très naturelle. Ils en parlent en profondeur ici easings.net
Erik B

1
J'ai essayé votre méthode, mais à la fin de l'animation, ma vue n'est plus visible.
Aman Verma

26

Ok, je viens de trouver une solution TRÈS 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
+1, même si cela est nommé laid, cela fonctionne pour une vue dont nous ne connaissons pas encore sa taille (par exemple, dans le cas où nous ajoutons une vue nouvellement créée (dont la taille est FILL_PARENT) au parent et que nous aimerions animer ce processus, y compris l'animation de la croissance de la taille des parents).
Vit Khudenko

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

22
public static void expand(final View v, int duration, int targetHeight) {
        v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, targetHeight);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (int) animation.getAnimatedValue();
                v.requestLayout();
            }
        });
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setDuration(duration);
        valueAnimator.start();
    }
public static void collapse(final View v, int duration, int targetHeight) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, targetHeight);
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (int) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(duration);
    valueAnimator.start();
}

1
J'ai ce problème ... le contenu de la vue pliante disparaît lors de l'expansion. J'ai Recycler View qui disparaît en développant cette vue. @LenaYan
Akshay Mahajan

21

Si vous ne voulez pas agrandir ou réduire complètement - voici une simple HeightAnimation -

import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;

public class HeightAnimation extends Animation {
    protected final int originalHeight;
    protected final View view;
    protected float perValue;

    public HeightAnimation(View view, int fromHeight, int toHeight) {
        this.view = view;
        this.originalHeight = fromHeight;
        this.perValue = (toHeight - fromHeight);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        view.getLayoutParams().height = (int) (originalHeight + perValue * interpolatedTime);
        view.requestLayout();
    }

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

Usage:

HeightAnimation heightAnim = new HeightAnimation(view, view.getHeight(), viewPager.getHeight() - otherView.getHeight());
heightAnim.setDuration(1000);
view.startAnimation(heightAnim);

13

J'ai adapté la réponse actuellement acceptée par Tom Esterez , qui fonctionnait mais avait une animation saccadée et pas très fluide. Ma solution remplace essentiellement le Animationpar un ValueAnimator, qui peut être équipé d'un Interpolatorde votre choix pour obtenir divers effets tels que dépassement, rebond, accélération, etc.

Cette solution fonctionne très bien avec les vues qui ont une hauteur dynamique (c'est-à-dire en utilisant WRAP_CONTENT), car elle mesure d'abord la hauteur réelle requise puis s'anime à cette hauteur.

public static void expand(final View v) {
    v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    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);

    ValueAnimator va = ValueAnimator.ofInt(1, targetHeight);
    va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    va.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationEnd(Animator animation) {
            v.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
        }

        @Override public void onAnimationStart(Animator animation) {}
        @Override public void onAnimationCancel(Animator animation) {}
        @Override public void onAnimationRepeat(Animator animation) {}
    });
    va.setDuration(300);
    va.setInterpolator(new OvershootInterpolator());
    va.start();
}

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

    ValueAnimator va = ValueAnimator.ofInt(initialHeight, 0);
    va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (Integer) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    va.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationEnd(Animator animation) {
            v.setVisibility(View.GONE);
        }

        @Override public void onAnimationStart(Animator animation) {}
        @Override public void onAnimationCancel(Animator animation) {}
        @Override public void onAnimationRepeat(Animator animation) {}
    });
    va.setDuration(300);
    va.setInterpolator(new DecelerateInterpolator());
    va.start();
}

Vous appelez alors simplement expand( myView );ou collapse( myView );.


Merci. Vous pouvez également ajouter une situation où la hauteur minimale n'est pas 0.
CoolMind

je travaille pour moi pour linearlayout
Roger

Je viens de corriger les paramètres utilisés v.measure()et maintenant cela fonctionne parfaitement. Merci!
Shahood ul Hassan

9

En utilisant les fonctions d'extension de Kotlin, ceci est testé et la réponse la plus courte

Il suffit d'appeler animateVisibility (développer / réduire) sur n'importe quelle vue.

fun View.animateVisibility(setVisible: Boolean) {
    if (setVisible) expand(this) else collapse(this)
}

private fun expand(view: View) {
    view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
    val initialHeight = 0
    val targetHeight = view.measuredHeight

    // Older versions of Android (pre API 21) cancel animations for views with a height of 0.
    //v.getLayoutParams().height = 1;
    view.layoutParams.height = 0
    view.visibility = View.VISIBLE

    animateView(view, initialHeight, targetHeight)
}

private fun collapse(view: View) {
    val initialHeight = view.measuredHeight
    val targetHeight = 0

    animateView(view, initialHeight, targetHeight)
}

private fun animateView(v: View, initialHeight: Int, targetHeight: Int) {
    val valueAnimator = ValueAnimator.ofInt(initialHeight, targetHeight)
    valueAnimator.addUpdateListener { animation ->
        v.layoutParams.height = animation.animatedValue as Int
        v.requestLayout()
    }
    valueAnimator.addListener(object : Animator.AnimatorListener {
        override fun onAnimationEnd(animation: Animator) {
            v.layoutParams.height = targetHeight
        }

        override fun onAnimationStart(animation: Animator) {}
        override fun onAnimationCancel(animation: Animator) {}
        override fun onAnimationRepeat(animation: Animator) {}
    })
    valueAnimator.duration = 300
    valueAnimator.interpolator = DecelerateInterpolator()
    valueAnimator.start()
}

Je voulais poster la même réponse :) Dommage que cela soit enterré si profondément ici.
muetzenflo

@muetzenflo Si de plus en plus de gens votent pour la réponse, elle apparaîtra. :)
Rajkiran

J'ai aimé cette solution jusqu'à ce que je réalise s'il y a une vue de texte avec plusieurs lignes avec une hauteur de wrap_content, lorsqu'elle est développée, la vue de texte n'affichera qu'une seule ligne. J'essaie de réparer maintenant
olearyj234

J'ai essayé, mais l'animation ne semble pas fluide. Pour développer, la totalité du texte s'affiche brièvement, puis l'animation est lue. Pour la réduction, la vue textuelle se développe à nouveau immédiatement après la réduction, pour une raison quelconque. Une idée de ce que je fais mal?
Anchith Acharya

7

En ajoutant à l' excellente réponse de Tom Esterez et à l' excellente mise à jour d' Erik B , j'ai pensé publier ma propre prise de vue, en compactant les méthodes d'extension et de contrat en une seule. De cette façon, vous pourriez par exemple avoir une action comme celle-ci ...

button.setOnClickListener(v -> expandCollapse(view));

... qui appelle la méthode ci-dessous et lui permet de savoir quoi faire après chaque onClick () ...

public static void expandCollapse(View view) {

    boolean expand = view.getVisibility() == View.GONE;
    Interpolator easeInOutQuart = PathInterpolatorCompat.create(0.77f, 0f, 0.175f, 1f);

    view.measure(
        View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
    );

    int height = view.getMeasuredHeight();
    int duration = (int) (height/view.getContext().getResources().getDisplayMetrics().density);

    Animation animation = new Animation() {
        @Override protected void applyTransformation(float interpolatedTime, Transformation t) {
            if (expand) {
                view.getLayoutParams().height = 1;
                view.setVisibility(View.VISIBLE);
                if (interpolatedTime == 1) {
                    view.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
                } else {
                    view.getLayoutParams().height = (int) (height * interpolatedTime);
                }
                view.requestLayout();
            } else {
                if (interpolatedTime == 1) {
                    view.setVisibility(View.GONE);
                } else {
                    view.getLayoutParams().height = height - (int) (height * interpolatedTime);
                    view.requestLayout();
                }
            }
        }
        @Override public boolean willChangeBounds() {
            return true;
        }
    };

    animation.setInterpolator(easeInOutQuart);
    animation.setDuration(duration);
    view.startAnimation(animation);

}

J'ai essayé ce code mais pour qu'il fonctionne sur plusieurs vues, vous devez faire défiler. Une idée de comment résoudre ce problème? stackoverflow.com/q/43916369/1009507
sammyukavi

@Ukavi J'utilise ceci avec plusieurs vues et cela fonctionne très bien dans un ScrollView.
mjp66

Et dans une vue de recyclage?
sammyukavi

@Ukavi n'a pas encore eu besoin de l'utiliser dans une vue d'ensemble du recyclage, mais je ne vois pas pourquoi cela ne fonctionnerait pas. Vous devrez l'expérimenter un peu vous-même;)
mjp66

6

Je voudrais ajouter quelque chose à la réponse très utile ci-dessus . Si vous ne connaissez pas la hauteur avec laquelle vous vous retrouverez, car votre vue .getHeight () renvoie 0, vous pouvez effectuer les opérations suivantes pour obtenir la hauteur:

contentView.measure(DUMMY_HIGH_DIMENSION, DUMMY_HIGH_DIMENSION);
int finalHeight = view.getMeasuredHeight();

Où DUMMY_HIGH_DIMENSIONS est la largeur / hauteur (en pixels) à laquelle votre vue est contrainte ... avoir un nombre énorme est raisonnable lorsque la vue est encapsulée avec un ScrollView.


6

Il s'agit d'un extrait que j'ai utilisé pour redimensionner la largeur d'une vue (LinearLayout) avec animation.

Le code est censé se développer ou se réduire en fonction de la taille cible. Si vous voulez une largeur fill_parent, vous devrez passer le parent .getMeasuredWidth comme largeur cible tout en définissant le drapeau sur true.

J'espère que cela aide certains d'entre vous.

public class WidthResizeAnimation extends Animation {
int targetWidth;
int originaltWidth;
View view;
boolean expand;
int newWidth = 0;
boolean fillParent;

public WidthResizeAnimation(View view, int targetWidth, boolean fillParent) {
    this.view = view;
    this.originaltWidth = this.view.getMeasuredWidth();
    this.targetWidth = targetWidth;
    newWidth = originaltWidth;
    if (originaltWidth > targetWidth) {
        expand = false;
    } else {
        expand = true;
    }
    this.fillParent = fillParent;
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    if (expand && newWidth < targetWidth) {
        newWidth = (int) (newWidth + (targetWidth - newWidth) * interpolatedTime);
    }

    if (!expand && newWidth > targetWidth) {
        newWidth = (int) (newWidth - (newWidth - targetWidth) * interpolatedTime);
    }
    if (fillParent && interpolatedTime == 1.0) {
        view.getLayoutParams().width = -1;

    } else {
        view.getLayoutParams().width = newWidth;
    }
    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;
}

}


Y a-t-il une astuce pour que cela fonctionne? La classe obtient les largeurs d'origine et cible correctes, mais mes vues ne seront pas redimensionnées. J'utilise resizeAnim.start(). Ont également essayé avec et sanssetFillAfter(true)
Ben Kane

Je l'ai. J'ai dû faire appel .startAnimation(resizeAnim)à la vue.
Ben Kane

6

Pour une animation fluide, veuillez utiliser le gestionnaire avec la méthode d'exécution ..... Et profitez de l'animation Développer / Réduire

    class AnimUtils{

                 public void expand(final View v) {
                  int ANIMATION_DURATION=500;//in milisecond
        v.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        final int targtetHeight = v.getMeasuredHeight();

        v.getLayoutParams().height = 0;
        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)(targtetHeight * interpolatedTime);
                v.requestLayout();
            }

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

        // 1dp/ms
        a.setDuration(ANIMATION_DURATION);

      // a.setDuration((int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density));
        v.startAnimation(a);
    }



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

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

Et appelez en utilisant ce code:

       private void setAnimationOnView(final View inactive ) {
    //I am applying expand and collapse on this TextView ...You can use your view 

    //for expand animation
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {

            new AnimationUtililty().expand(inactive);

        }
    }, 1000);


    //For collapse
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {

            new AnimationUtililty().collapse(inactive);
            //inactive.setVisibility(View.GONE);

        }
    }, 8000);

}

Une autre solution est:

               public void expandOrCollapse(final View v,String exp_or_colpse) {
    TranslateAnimation anim = null;
    if(exp_or_colpse.equals("expand"))
    {
        anim = new TranslateAnimation(0.0f, 0.0f, -v.getHeight(), 0.0f);
        v.setVisibility(View.VISIBLE);  
    }
    else{
        anim = new TranslateAnimation(0.0f, 0.0f, 0.0f, -v.getHeight());
        AnimationListener collapselistener= new AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
            v.setVisibility(View.GONE);
            }
        };

        anim.setAnimationListener(collapselistener);
    }

     // To Collapse
        //

    anim.setDuration(300);
    anim.setInterpolator(new AccelerateInterpolator(0.5f));
    v.startAnimation(anim);
}

5

solutions combinées de @Tom Esterez et @Geraldo Neto

public static void expandOrCollapseView(View v,boolean expand){

    if(expand){
        v.measure(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
        final int targetHeight = v.getMeasuredHeight();
        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        ValueAnimator valueAnimator = ValueAnimator.ofInt(targetHeight);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (int) animation.getAnimatedValue();
                v.requestLayout();
            }
        });
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setDuration(500);
        valueAnimator.start();
    }
    else
    {
        final int initialHeight = v.getMeasuredHeight();
        ValueAnimator valueAnimator = ValueAnimator.ofInt(initialHeight,0);
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (int) animation.getAnimatedValue();
                v.requestLayout();
                if((int)animation.getAnimatedValue() == 0)
                    v.setVisibility(View.GONE);
            }
        });
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setDuration(500);
        valueAnimator.start();
    }
}

//sample usage
expandOrCollapseView((Your ViewGroup),(Your ViewGroup).getVisibility()!=View.VISIBLE);

4

Oui, je suis d'accord avec les commentaires ci-dessus. Et en effet, il semble que la bonne chose (ou du moins la plus simple?) À faire est de spécifier (en XML) une hauteur de mise en page initiale de "0px" - et ensuite vous pouvez passer un autre argument pour "toHeight" ( c'est-à-dire la "hauteur finale") pour le constructeur de votre sous-classe d'animation personnalisée, par exemple dans l'exemple ci-dessus, cela ressemblerait à ceci:

    public DropDownAnim( View v, int toHeight ) { ... }

En tout cas, j'espère que ça aide! :)


4

Voici ma solution. Je pense que c'est plus simple. Il élargit seulement la vue mais peut facilement être étendu.

public class WidthExpandAnimation extends Animation
{
    int _targetWidth;
    View _view;

    public WidthExpandAnimation(View view)
    {
        _view = view;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t)
    {
        if (interpolatedTime < 1.f)
        {
            int newWidth = (int) (_targetWidth * interpolatedTime);

            _view.layout(_view.getLeft(), _view.getTop(),
                    _view.getLeft() + newWidth, _view.getBottom());
        }
        else
            _view.requestLayout();
    }

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

        _targetWidth = width;
    }

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

4

Je pense que la solution la plus simple consiste à définir android:animateLayoutChanges="true"votre vue LinearLayout, puis à afficher / masquer la vue en définissant sa visibilité. Fonctionne comme un charme, mais vous n'avez aucun contrôle sur la durée de l'animation


3

Tu es sur la bonne piste. Assurez-vous que la v1 est définie pour avoir une hauteur de mise en page de zéro juste avant le début de l'animation. Vous souhaitez initialiser votre configuration pour qu'elle ressemble à la première image de l'animation avant de démarrer l'animation.


Je suis d'accord mais comment obtenir initialHeight (requis par mon animation) si je fais cela?
Tom Esterez

Avez-vous essayé d'enregistrer la hauteur initiale dans initialize, de définir la vue visible à cet endroit, puis de définir v.getLayoutParams (). Height = 0; directement après, tout en initialiser?
Micah Hainline

Oui, si je le fais, la méthode d'initialisation est appelée avec height = 0
Tom Esterez

3

Ce fut ma solution, mon ImageViewpasse de 100%à 200%et revient à sa taille d'origine, en utilisant deux fichiers d'animation à l'intérieur du res/anim/dossier

anim_grow.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:interpolator="@android:anim/accelerate_interpolator">
 <scale
  android:fromXScale="1.0"
  android:toXScale="2.0"
  android:fromYScale="1.0"
  android:toYScale="2.0"
  android:duration="3000"
  android:pivotX="50%"
  android:pivotY="50%"
  android:startOffset="2000" />
</set>

anim_shrink.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:interpolator="@android:anim/accelerate_interpolator">
 <scale
  android:fromXScale="2.0"
  android:toXScale="1.0"
  android:fromYScale="2.0"
  android:toYScale="1.0"
  android:duration="3000"
  android:pivotX="50%"
  android:pivotY="50%"
  android:startOffset="2000" />
</set>

Envoyer un ImageViewà ma méthodesetAnimationGrowShrink()

ImageView img1 = (ImageView)findViewById(R.id.image1);
setAnimationGrowShrink(img1);

setAnimationGrowShrink() méthode:

private void setAnimationGrowShrink(final ImageView imgV){
    final Animation animationEnlarge = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.anim_grow);
    final Animation animationShrink = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.anim_shrink);

    imgV.startAnimation(animationEnlarge);

    animationEnlarge.setAnimationListener(new AnimationListener() {         
        @Override
        public void onAnimationStart(Animation animation) {}

        @Override
        public void onAnimationRepeat(Animation animation) {}

        @Override
        public void onAnimationEnd(Animation animation) {
            imgV.startAnimation(animationShrink);
        }
    });

    animationShrink.setAnimationListener(new AnimationListener() {          
        @Override
        public void onAnimationStart(Animation animation) {}

        @Override
        public void onAnimationRepeat(Animation animation) {}

        @Override
        public void onAnimationEnd(Animation animation) {
            imgV.startAnimation(animationEnlarge);
        }
    });

}

3

C'est une bonne solution de travail, je l'ai testée:

Exapnd:

private void expand(View v) {
    v.setVisibility(View.VISIBLE);

    v.measure(View.MeasureSpec.makeMeasureSpec(PARENT_VIEW.getWidth(), View.MeasureSpec.EXACTLY),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

    final int targetHeight = v.getMeasuredHeight();

    mAnimator = slideAnimator(0, targetHeight);
    mAnimator.setDuration(800);
    mAnimator.start();
}

Effondrer:

private void collapse(View v) {
    int finalHeight = v.getHeight();

    mAnimator = slideAnimator(finalHeight, 0);

    mAnimator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animator) {

        }

        @Override
        public void onAnimationEnd(Animator animator) {
            //Height=0, but it set visibility to GONE
            llDescp.setVisibility(View.GONE);
        }

        @Override
        public void onAnimationCancel(Animator animator) {

        }

        @Override
        public void onAnimationRepeat(Animator animator) {

        }
    });
    mAnimator.start();
}

Animateur de valeur:

private ValueAnimator slideAnimator(int start, int end) {
    ValueAnimator mAnimator = ValueAnimator.ofInt(start, end);

    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            //Update Height
            int value = (Integer) valueAnimator.getAnimatedValue();
            ViewGroup.LayoutParams layoutParams = llDescp.getLayoutParams();
            layoutParams.height = value;
            v.setLayoutParams(layoutParams);
        }
    });
    return mAnimator;
}

La vue v est la vue à animer, PARENT_VIEW est la vue conteneur contenant la vue.


2

C'est vraiment simple avec droidQuery . Pour commencer, considérez cette disposition:

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
    <LinearLayout
        android:id="@+id/v1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:text="View 1" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/v2"
        android:layout_width="wrap_content"
        android:layout_height="0dp" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:text="View 2" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:text="View 3" />
    </LinearLayout>
</LinearLayout>

Nous pouvons animer la hauteur à la valeur souhaitée - disons 100dp- en utilisant le code suivant:

//convert 100dp to pixel value
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());

Utilisez ensuite droidQuerypour animer. La manière la plus simple est avec ceci:

$.animate("{ height: " + height + "}", new AnimationOptions());

Pour rendre l'animation plus attrayante, pensez à ajouter un assouplissement:

$.animate("{ height: " + height + "}", new AnimationOptions().easing($.Easing.BOUNCE));

Vous pouvez également modifier la durée d' AnimationOptionsutilisation de la duration()méthode ou gérer ce qui se passe à la fin de l'animation. Pour un exemple complexe, essayez:

$.animate("{ height: " + height + "}", new AnimationOptions().easing($.Easing.BOUNCE)
                                                             .duration(1000)
                                                             .complete(new Function() {
                                                                 @Override
                                                                 public void invoke($ d, Object... args) {
                                                                     $.toast(context, "finished", Toast.LENGTH_SHORT);
                                                                 }
                                                             }));

2

Meilleure solution pour développer / réduire les vues:

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        View view = buttonView.getId() == R.id.tb_search ? fSearch : layoutSettings;
        transform(view, 200, isChecked
            ? ViewGroup.LayoutParams.WRAP_CONTENT
            : 0);
    }

    public static void transform(final View v, int duration, int targetHeight) {
        int prevHeight  = v.getHeight();
        v.setVisibility(View.VISIBLE);
        ValueAnimator animator;
        if (targetHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
            v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            animator = ValueAnimator.ofInt(prevHeight, v.getMeasuredHeight());
        } else {
            animator = ValueAnimator.ofInt(prevHeight, targetHeight);
        }
        animator.addUpdateListener(animation -> {
            v.getLayoutParams().height = (animation.getAnimatedFraction() == 1.0f)
                    ? targetHeight
                    : (int) animation.getAnimatedValue();
            v.requestLayout();
        });
        animator.setInterpolator(new LinearInterpolator());
        animator.setDuration(duration);
        animator.start();
    }

Bien que cela fonctionne, cela dépend aussi des paramètres du développeur (durée de l'animation). Et peaufinez votre code, supprimez la fonction lambda et reformatez onCheckedChanged.
CoolMind

Pourquoi suffit-il d'appeler requestLayout uniquement sur v après avoir modifié les LayoutParams de v? J'ai pensé qu'il serait nécessaire d'appeler requestLayout sur le parent de v
vlazzle

2

Vous pouvez utiliser un ViewPropertyAnimator avec une légère torsion. Pour réduire, redimensionnez la vue à une hauteur de 1 pixel, puis masquez-la. Pour l'agrandir, l'afficher, puis l'agrandir à sa hauteur.

private void collapse(final View view) {
    view.setPivotY(0);
    view.animate().scaleY(1/view.getHeight()).setDuration(1000).withEndAction(new Runnable() {
        @Override public void run() {
            view.setVisibility(GONE);
        }
    });
}

private void expand(View view, int height) {
    float scaleFactor = height / view.getHeight();

    view.setVisibility(VISIBLE);
    view.setPivotY(0);
    view.animate().scaleY(scaleFactor).setDuration(1000);
}

Le pivot indique à la vue à partir de quelle échelle, la valeur par défaut est au milieu. La durée est facultative (par défaut = 1000). Vous pouvez également définir l'interpolateur à utiliser, comme.setInterpolator(new AccelerateDecelerateInterpolator())


1

J'ai créé une version dans laquelle vous n'avez pas besoin de spécifier la hauteur de la mise en page, c'est donc beaucoup plus facile et plus propre à utiliser. La solution est d'obtenir la hauteur dans la première image de l'animation (elle est disponible à ce moment, du moins lors de mes tests). De cette façon, vous pouvez fournir une vue avec une hauteur et une marge inférieure arbitraires.

Il y a aussi un petit piratage dans le constructeur - la marge inférieure est définie sur -10000 afin que la vue reste cachée avant la transformation (empêche le scintillement).

public class ExpandAnimation extends Animation {


    private View mAnimatedView;
    private ViewGroup.MarginLayoutParams mViewLayoutParams;
    private int mMarginStart, mMarginEnd;

    public ExpandAnimation(View view) {
        mAnimatedView = view;
        mViewLayoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
        mMarginEnd = mViewLayoutParams.bottomMargin;
        mMarginStart = -10000; //hide before viewing by settings very high negative bottom margin (hack, but works nicely)
        mViewLayoutParams.bottomMargin = mMarginStart;
        mAnimatedView.setLayoutParams(mViewLayoutParams);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
            //view height is already known when the animation starts
            if(interpolatedTime==0){
                mMarginStart = -mAnimatedView.getHeight();
            }
            mViewLayoutParams.bottomMargin = (int)((mMarginEnd-mMarginStart) * interpolatedTime)+mMarginStart;
            mAnimatedView.setLayoutParams(mViewLayoutParams);
    }
}

1

Utilisez ValueAnimator:

ValueAnimator expandAnimation = ValueAnimator.ofInt(mainView.getHeight(), 400);
expandAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(final ValueAnimator animation) {
        int height = (Integer) animation.getAnimatedValue();
        RelativeLayout.LayoutParams lp = (LayoutParams) mainView.getLayoutParams();
        lp.height = height;
    }
});


expandAnimation.setDuration(500);
expandAnimation.start();

Dans mon cas, ne fait rien. Vous pouvez également alléger votre code en réduisant 2 lignes mainView.getLayoutParams().height = height.
CoolMind

1
public static void slide(View v, int speed, int pos) {
    v.animate().setDuration(speed);
    v.animate().translationY(pos);
    v.animate().start();
}

// slide down
slide(yourView, 250, yourViewHeight);
// slide up
slide(yourView, 250, 0);

1
/**
 * Animation that either expands or collapses a view by sliding it down to make
 * it visible. Or by sliding it up so it will hide. It will look like it slides
 * behind the view above.
 * 
 */
public class FinalExpandCollapseAnimation extends Animation
{
    private View mAnimatedView;
    private int mEndHeight;
    private int mType;
    public final static int COLLAPSE = 1;
    public final static int EXPAND = 0;
    private LinearLayout.LayoutParams mLayoutParams;
    private RelativeLayout.LayoutParams mLayoutParamsRel;
    private String layout;
    private Context context;

    /**
     * Initializes expand collapse animation, has two types, collapse (1) and
     * expand (0).
     * 
     * @param view
     *            The view to animate
     * @param type
     *            The type of animation: 0 will expand from gone and 0 size to
     *            visible and layout size defined in xml. 1 will collapse view
     *            and set to gone
     */
    public FinalExpandCollapseAnimation(View view, int type, int height, String layout, Context context)
    {
        this.layout = layout;
        this.context = context;
        mAnimatedView = view;
        mEndHeight = mAnimatedView.getMeasuredHeight();
        if (layout.equalsIgnoreCase("linear"))
            mLayoutParams = ((LinearLayout.LayoutParams) view.getLayoutParams());
        else
            mLayoutParamsRel = ((RelativeLayout.LayoutParams) view.getLayoutParams());
        mType = type;
        if (mType == EXPAND)
        {
            AppConstant.ANIMATED_VIEW_HEIGHT = height;
        }
        else
        {
            if (layout.equalsIgnoreCase("linear"))
                mLayoutParams.topMargin = 0;
            else
                mLayoutParamsRel.topMargin = convertPixelsIntoDensityPixels(36);
        }
        setDuration(600);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t)
    {
        super.applyTransformation(interpolatedTime, t);
        if (interpolatedTime < 1.0f)
        {
            if (mType == EXPAND)
            {
                if (layout.equalsIgnoreCase("linear"))
                {
                    mLayoutParams.height = AppConstant.ANIMATED_VIEW_HEIGHT
                            + (-AppConstant.ANIMATED_VIEW_HEIGHT + (int) (AppConstant.ANIMATED_VIEW_HEIGHT * interpolatedTime));
                }
                else
                {
                    mLayoutParamsRel.height = AppConstant.ANIMATED_VIEW_HEIGHT
                            + (-AppConstant.ANIMATED_VIEW_HEIGHT + (int) (AppConstant.ANIMATED_VIEW_HEIGHT * interpolatedTime));
                }
                mAnimatedView.setVisibility(View.VISIBLE);
            }
            else
            {
                if (layout.equalsIgnoreCase("linear"))
                    mLayoutParams.height = mEndHeight - (int) (mEndHeight * interpolatedTime);
                else
                    mLayoutParamsRel.height = mEndHeight - (int) (mEndHeight * interpolatedTime);
            }
            mAnimatedView.requestLayout();
        }
        else
        {
            if (mType == EXPAND)
            {
                if (layout.equalsIgnoreCase("linear"))
                {
                    mLayoutParams.height = AppConstant.ANIMATED_VIEW_HEIGHT;
                    mLayoutParams.topMargin = 0;
                }
                else
                {
                    mLayoutParamsRel.height = AppConstant.ANIMATED_VIEW_HEIGHT;
                    mLayoutParamsRel.topMargin = convertPixelsIntoDensityPixels(36);
                }
                mAnimatedView.setVisibility(View.VISIBLE);
                mAnimatedView.requestLayout();
            }
            else
            {
                if (layout.equalsIgnoreCase("linear"))
                    mLayoutParams.height = 0;
                else
                    mLayoutParamsRel.height = 0;
                mAnimatedView.setVisibility(View.GONE);
                mAnimatedView.requestLayout();
            }
        }
    }

    private int convertPixelsIntoDensityPixels(int pixels)
    {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        return (int) metrics.density * pixels;
    }
}

La classe peut être appelée de la manière suivante

   if (findViewById(R.id.ll_specailoffer_show_hide).getVisibility() == View.VISIBLE) {
                        ((ImageView) findViewById(R.id.iv_specialhour_seemore)).setImageResource(R.drawable.white_dropdown_up);

                        FinalExpandCollapseAnimation finalExpandCollapseAnimation = new FinalExpandCollapseAnimation(
                                findViewById(R.id.ll_specailoffer_show_hide),
                                FinalExpandCollapseAnimation.COLLAPSE,
                                SpecialOfferHeight, "linear", this);
                        findViewById(R.id.ll_specailoffer_show_hide)
                                .startAnimation(finalExpandCollapseAnimation);
                        ((View) findViewById(R.id.ll_specailoffer_show_hide).getParent()).invalidate();
                    } else {
                        ((ImageView) findViewById(R.id.iv_specialhour_seemore)).setImageResource(R.drawable.white_dropdown);

                        FinalExpandCollapseAnimation finalExpandCollapseAnimation = new FinalExpandCollapseAnimation(
                                findViewById(R.id.ll_specailoffer_show_hide),
                                FinalExpandCollapseAnimation.EXPAND,
                                SpecialOfferHeight, "linear", this);
                        findViewById(R.id.ll_specailoffer_show_hide)
                                .startAnimation(finalExpandCollapseAnimation);
                        ((View) findViewById(R.id.ll_specailoffer_show_hide).getParent()).invalidate();
                    }

1

Basé sur les solutions de @Tom Esterez et @Seth Nelson (top 2), je les ai simplifiées. En plus des solutions originales, cela ne dépend pas des options du développeur (paramètres d'animation).

private void resizeWithAnimation(final View view, int duration, final int targetHeight) {
    final int initialHeight = view.getMeasuredHeight();
    final int distance = targetHeight - initialHeight;

    view.setVisibility(View.VISIBLE);

    Animation a = new Animation() {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            if (interpolatedTime == 1 && targetHeight == 0) {
                view.setVisibility(View.GONE);
            }
            view.getLayoutParams().height = (int) (initialHeight + distance * interpolatedTime);
            view.requestLayout();
        }

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

    a.setDuration(duration);
    view.startAnimation(a);
}

Eh bien, après 3 ans, j'ai testé à nouveau plusieurs solutions, mais seule la mienne a bien fonctionné.
CoolMind
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.