¿Cómo animo View.setVisibility (GONE)?

84

Quiero hacer un Animationpara cuando a Viewtenga su visibilidad configurada GONE. En lugar de simplemente desaparecer, Viewdebería "colapsar". Intenté esto con a, ScaleAnimationpero luego Viewse contrajo, pero el diseño solo cambiará el tamaño de su espacio después (o antes) de las Animationparadas (o inicios).

¿Cómo puedo hacer Animationque, durante la animación, las letras inferiores Viewqueden directamente debajo del contenido, en lugar de tener un espacio en blanco?

MrCopo de nieve
fuente
He usado la misma técnica, como Andy ha presentado aquí, en mi ExpandAnimation: udinic.wordpress.com/2011/09/03/expanding-listview-items No usé una animación a escala, acabo de construir una nueva animación clase para eso.
Udinic
Esto fue muy útil mientras intentaba hacer esto. Gracias
atraudes
Excelente Udinic .. realmente resolvió mi problema .. :) gracias
Yousuf Qureshi
Genial, necesito adaptarme a mi problema, pero al final funciona. Para mí, esta solución fue mejor que la otra respuesta.
Derzu

Respuestas:

51

No parece haber una manera fácil de hacer esto a través de la API, porque la animación simplemente cambia la matriz de representación de la vista, no el tamaño real. Pero podemos establecer un margen negativo para engañar a LinearLayout haciéndole pensar que la vista es cada vez más pequeña.

Por lo tanto, recomiendo crear su propia clase de animación, basada en ScaleAnimation, y anular el método "applyTransformation" para establecer nuevos márgenes y actualizar el diseño. Me gusta esto...

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

    }

}

Se aplica la advertencia habitual: debido a que anulamos un método protegido (applyTransformation), no se garantiza que funcione en futuras versiones de Android.

Andy
fuente
3
¡¿Por qué diablos no pensé en esto ?! Gracias. Además, no entiendo: "Se aplica la advertencia habitual: debido a que estamos anulando un método protegido (applyTransformation), no se garantiza que funcione en futuras versiones de Android". - ¿Por qué las funciones protegidas difieren entre las versiones de API? Esos no están ocultos y se implementan protegidos para que pueda anularlos (de lo contrario, serían del ámbito del paquete).
MrSnowflake
Probablemente tengas razón sobre el método protegido. Tiendo a ser demasiado cauteloso al acceder a ellos en una API.
Andy
1
Esto funcionó muy bien para mí, excepto que para que el "colapso" funcione (fromY = 0.0f, toY = 1.0f), tuve que eliminar el 0 - en el marginBottomToYcálculo.
dmon
2
Sugeriría usar el tipo genérico en MarginLayoutParamslugar de convertirlo en un LayoutParamtipo específico .
Paul Lammertsma
3
Supongamos que tengo que alternar la animación, entonces, ¿cómo podría hacerlo al revés? Por favor aconséjame.
Umesh
99

Coloque la vista en un diseño si no es así y configúrelo android:animateLayoutChanges="true"para ese diseño.

Vinay W
fuente
1
¡La API mínima requerida es 11 o superior! No se puede utilizar este método para una versión inferior.
Nagaraj Alagusudaram
2
este es el atributo de diseño más subestimado ... ¡gracias!
Andrés Santiago
7

Usé la misma técnica que Andy ha presentado aquí. Escribí mi propia clase de animación para eso, que anima el valor del margen, haciendo que el efecto del elemento desaparezca / aparezca. Se parece a esto:

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

Tengo un ejemplo completo que funciona en mi publicación de blog http://udinic.wordpress.com/2011/09/03/expanding-listview-items/

Udinic
fuente
2

Usé la misma técnica que Andy aquí y la refiné para que pueda usarse para expandir y colapsar sin fallas, también usando una técnica que se describe aquí: 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();
    }
}
Aprenda OpenGL ES
fuente
¿Es posible usar esto para contraer primero un LinearLayout existente y luego expandir el mismo LinearLayout nuevamente después? Cuando trato de hacer esto, simplemente se colapsa y no se expande nuevamente (probablemente porque la altura de la vista ahora es 0 o algo así).
AHaahr
Descubrí que funciona de manera más confiable cuando el diseño lineal contiene más de una vista. Si contiene solo una vista, no siempre se expandirá.
Aprenda OpenGL ES