Comment animer View.setVisibility (GONE)

84

Je veux faire un Animationpour quand a Viewobtient sa visibilité définie sur GONE. Au lieu de simplement disparaître, le Viewdevrait «s'effondrer». J'ai essayé cela avec un ScaleAnimationmais alors le Viewest réduit, mais la mise en page ne redimensionnera son espace qu'après (ou avant) l' Animationarrêt (ou le démarrage).

Comment puis-je faire en Animationsorte que, pendant l'animation, les Views inférieurs restent directement sous le contenu, au lieu d'avoir un espace vide?

MrSnowflake
la source
J'ai utilisé la même technique, comme Andy l'a présenté ici, sur mon ExpandAnimation: udinic.wordpress.com/2011/09/03/expanding-listview-items Je n'ai pas utilisé une animation à l'échelle, je viens de construire une nouvelle animation classe pour ça.
Udinic
Cela a été très utile pendant que j'essayais de le faire. Merci
atraudes
Excellent Udinic .. a vraiment résolu mon problème .. :) merci
Yousuf Qureshi
Super, j'ai besoin de m'adapter à mon problème, mais au final ça marche. Pour moi, cette solution était meilleure que l'autre réponse.
Derzu

Réponses:

51

Il ne semble pas y avoir de moyen facile de le faire via l'API, car l'animation change simplement la matrice de rendu de la vue, pas la taille réelle. Mais nous pouvons définir une marge négative pour tromper LinearLayout en pensant que la vue devient plus petite.

Je vous recommande donc de créer votre propre classe d'animation, basée sur ScaleAnimation, et de remplacer la méthode "applyTransformation" pour définir de nouvelles marges et mettre à jour la mise en page. Comme ça...

public class Q2634073 extends Activity implements OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.q2634073);
        findViewById(R.id.item1).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        view.startAnimation(new MyScaler(1.0f, 1.0f, 1.0f, 0.0f, 500, view, true));
    }

    public class MyScaler extends ScaleAnimation {

        private View mView;

        private LayoutParams mLayoutParams;

        private int mMarginBottomFromY, mMarginBottomToY;

        private boolean mVanishAfter = false;

        public MyScaler(float fromX, float toX, float fromY, float toY, int duration, View view,
                boolean vanishAfter) {
            super(fromX, toX, fromY, toY);
            setDuration(duration);
            mView = view;
            mVanishAfter = vanishAfter;
            mLayoutParams = (LayoutParams) view.getLayoutParams();
            int height = mView.getHeight();
            mMarginBottomFromY = (int) (height * fromY) + mLayoutParams.bottomMargin - height;
            mMarginBottomToY = (int) (0 - ((height * toY) + mLayoutParams.bottomMargin)) - height;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            if (interpolatedTime < 1.0f) {
                int newMarginBottom = mMarginBottomFromY
                        + (int) ((mMarginBottomToY - mMarginBottomFromY) * interpolatedTime);
                mLayoutParams.setMargins(mLayoutParams.leftMargin, mLayoutParams.topMargin,
                    mLayoutParams.rightMargin, newMarginBottom);
                mView.getParent().requestLayout();
            } else if (mVanishAfter) {
                mView.setVisibility(View.GONE);
            }
        }

    }

}

La mise en garde habituelle s'applique: parce que nous surchargons une méthode protégée (applyTransformation), cela n'est pas garanti de fonctionner dans les futures versions d'Android.

Andy
la source
3
Pourquoi diable n'ai-je pas pensé à ça?! Merci. De plus, je n'obtiens pas le: "La mise en garde habituelle s'applique: parce que nous remplaçons une méthode protégée (applyTransformation), cela n'est pas garanti de fonctionner dans les futures versions d'Android. - Pourquoi les fonctions protégées diffèrent-elles entre les versions d'API? Celles-ci ne sont pas cachées et sont implémentées protégées afin que vous puissiez les remplacer (sinon elles seraient étendues au package).
MrSnowflake
Vous avez probablement raison sur la méthode protégée. J'ai tendance à être trop prudent pour y accéder dans une API.
Andy
1
Cela a très bien fonctionné pour moi, sauf que pour que la "réduction" fonctionne (de Y = 0.0f, àY = 1.0f), j'ai dû supprimer le 0 - dans le marginBottomToYcalcul.
dmon
2
Je suggérerais d'utiliser le type générique MarginLayoutParamsau lieu de le convertir en un LayoutParamtype spécifique .
Paul Lammertsma
3
Supposons que je doive faire une animation à bascule, alors comment pourrais-je le faire à l'envers. S'il vous plaît des conseils.
Umesh
99

Mettez la vue dans une mise en page si ce n'est pas le cas et définie android:animateLayoutChanges="true"pour cette mise en page.

Vinay W
la source
1
L'API minimale requise est de 11 ou plus! Impossible d'utiliser cette méthode pour la version inférieure.
Nagaraj Alagusudaram
2
c'est l'attribut de mise en page le plus sous-estimé ... merci!
Andres Santiago
7

J'ai utilisé la même technique qu'Andy a présentée ici. J'ai écrit ma propre classe d'animation pour cela, qui anime la valeur de la marge, faisant disparaître / apparaître l'effet de l'élément. Cela ressemble à ceci:

public class ExpandAnimation extends Animation {

// Initializations...

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    super.applyTransformation(interpolatedTime, t);

    if (interpolatedTime < 1.0f) {

        // Calculating the new bottom margin, and setting it
        mViewLayoutParams.bottomMargin = mMarginStart
                + (int) ((mMarginEnd - mMarginStart) * interpolatedTime);

        // Invalidating the layout, making us seeing the changes we made
        mAnimatedView.requestLayout();
    }
}
}

J'ai un exemple complet qui fonctionne sur mon article de blog http://udinic.wordpress.com/2011/09/03/expanding-listview-items/

Udinic
la source
2

J'ai utilisé la même technique qu'Andy ici, et je l'ai affinée afin qu'elle puisse être utilisée pour l'expansion et la réduction sans problèmes, également en utilisant une technique décrite ici: https://stackoverflow.com/a/11426510/1317564

import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.ScaleAnimation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;

class LinearLayoutVerticalScaleAnimation extends ScaleAnimation {
    private final LinearLayout view;
    private final LinearLayout.LayoutParams layoutParams;

    private final float beginY;
    private final float endY;
    private final int originalBottomMargin;

    private int expandedHeight;
    private boolean marginsInitialized = false;
    private int marginBottomBegin;
    private int marginBottomEnd;

    private ViewTreeObserver.OnPreDrawListener preDrawListener;

    LinearLayoutVerticalScaleAnimation(float beginY, float endY,
            LinearLayout linearLayout) {
        super(1f, 1f, beginY, endY);

        this.view = linearLayout;
        this.layoutParams = (LinearLayout.LayoutParams) linearLayout.getLayoutParams();

        this.beginY = beginY;
        this.endY = endY;
        this.originalBottomMargin = layoutParams.bottomMargin;

        if (view.getHeight() != 0) {
            expandedHeight = view.getHeight();
            initializeMargins();
        }
    }

    private void initializeMargins() {
        final int beginHeight = (int) (expandedHeight * beginY);
        final int endHeight = (int) (expandedHeight * endY);

        marginBottomBegin = beginHeight + originalBottomMargin - expandedHeight;
        marginBottomEnd = endHeight + originalBottomMargin - expandedHeight;
        marginsInitialized = true;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);     

        if (!marginsInitialized && preDrawListener == null) {                       
            // To avoid glitches, don't draw until we've initialized everything.
            preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {                    
                    if (view.getHeight() != 0) {
                        expandedHeight = view.getHeight();
                        initializeMargins();
                        adjustViewBounds(0f);
                        view.getViewTreeObserver().removeOnPreDrawListener(this);                               
                    }

                    return false;
                }
            };

            view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);                   
        }

        if (interpolatedTime < 1.0f && view.getVisibility() != View.VISIBLE) {          
            view.setVisibility(View.VISIBLE);           
        }

        if (marginsInitialized) {           
            if (interpolatedTime < 1.0f) {
                adjustViewBounds(interpolatedTime);
            } else if (endY <= 0f && view.getVisibility() != View.GONE) {               
                view.setVisibility(View.GONE);
            }
        }
    }

    private void adjustViewBounds(float interpolatedTime) {
        layoutParams.bottomMargin = 
                marginBottomBegin + (int) ((marginBottomEnd - marginBottomBegin) * interpolatedTime);       

        view.getParent().requestLayout();
    }
}
Apprenez OpenGL ES
la source
Est-il possible de l'utiliser pour réduire d'abord un LinearLayout existant, puis développer à nouveau le même LinearLayout par la suite? Quand j'essaye de faire ceci, il s'effondre juste et ne se développe plus (probablement parce que la hauteur de la vue est maintenant 0 ou quelque chose comme ça).
AHaahr
J'ai trouvé que cela fonctionne de manière plus fiable lorsque la mise en page linéaire contient plusieurs vues. S'il ne contient qu'une seule vue, il ne se développera pas toujours.
Apprendre OpenGL ES