Android: ampliar / contraer animación

449

Digamos que tengo un diseño lineal vertical con:

[v1]
[v2]

Por defecto v1 tiene visibily = GONE. Me gustaría mostrar v1 con una animación expandida y presionar hacia abajo v2 al mismo tiempo.

Intenté algo como esto:

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

Pero con esta solución, parpadeo cuando comienza la animación. Creo que se debe a que v1 muestra el tamaño completo antes de que se aplique la animación.

Con javascript, esta es una línea de jQuery! ¿Alguna forma simple de hacer esto con Android?

Tom Esterez
fuente

Respuestas:

734

Veo que esta pregunta se hizo popular, así que publico mi solución real. La principal ventaja es que no tiene que conocer la altura expandida para aplicar la animación y una vez que la vista se expande, se adapta la altura si el contenido cambia. Funciona muy bien para mí.

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

Como mencionó @Jefferson en los comentarios, puede obtener una animación más suave cambiando la duración (y, por lo tanto, la velocidad) de la animación. Actualmente, se ha configurado a una velocidad de 1dp / ms

Tom Esterez
fuente
13
v.measure (MeasureSpec.makeMeasureSpec (LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec (LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); En algunos casos (mi - ListView) este desajuste conduce a un valor de targtetHeight incorrecto
Johnny Doe
12
@ Tom Esterez Esto funciona, pero no muy bien. ¿Hay algún trabajo adicional para hacerlo sin problemas?
acntwww
99
@acntwww Puede obtener una animación fluida multiplicando la duración por algún factor, como 4.a.setDuration(((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density)) * 4)
Jefferson Henrique C. Soares
10
@Alioo, importa android.view.animation.Transformation;
Jomia
55
¡Funciona genial! Tuve problemas con la altura medida ya que quería expandir un elemento dp fijo, así que cambié la medida v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));y ¡ v.getLayoutParams().height = interpolatedTime == 1 ? targetHeight : (int)(targetHeight * interpolatedTime);Eso funcionó para mí!
vkislicins
140

Estaba tratando de hacer lo que creo que era una animación muy similar y encontré una solución elegante. Este código asume que siempre vas de 0-> ho h-> 0 (h es la altura máxima). Los tres parámetros del constructor son view = la vista que se va a animar (en mi caso, una vista web), targetHeight = la altura máxima de la vista y down = un valor booleano que especifica la dirección (true = expandiendo, false = colapsando).

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;
    }
}
Seth Nelson
fuente
55
Hay un error tipográfico en el código: el nombre del método "inicializar" debe ser "inicializar" o no se llamará. ;) Recomiendo usar @Override en el futuro para que este tipo de error sea detectado por el compilador.
Lorne Laliberte
44
Estoy haciendo lo siguiente: "DropDownAnim anim = new DropDownAnim (grid_titulos_atual, GRID_HEIGHT, true); anim.setDuration (500); anim.start ();" Pero no está funcionando. Puse algunos puntos de interrupción en applyTransformation pero nunca se alcanzan
Paulo Cesar
55
Ops, funcionó, es view.startAnimation (a) ... El rendimiento no es muy bueno, pero funciona :)
Paulo Cesar
3
@IamStalker En esa situación, probablemente debería inicializar con dos variables, beginHeight y endingHeight. Luego cambie a: if (down) {newHeight = (int) (((endingHeight-startingHeight) * interpolatedTime) + startingHeight); } else {newHeight = (int) (((endingHeight-startingHeight) * (1 - interpolatedTime)) + startingHeight); }
Seth Nelson
3
@Seth Creo que newHeight puede ser simplemente (int) (((targetHeight -startingHeight) * interpolatedTime) + startingHeight), sin importar la dirección, siempre que beginHeight esté configurado en initialize ().
Giorgos Kylafas
138

Hoy me topé con el mismo problema y creo que la verdadera solución a esta pregunta es esta

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

Tendrá que establecer esta propiedad para todos los diseños superiores, que están involucrados en el turno. Si ahora configura la visibilidad de un diseño como GONE, el otro ocupará el espacio a medida que el que desaparece lo libere. Habrá una animación predeterminada que es una especie de "desvanecimiento", pero creo que puede cambiar esto, pero la última que no he probado, por ahora.

Sr. Fu
fuente
2
+1, ahora estoy buscando Velocidad: duración de animateLayoutChanges
Tushar Pandey
99
Animar cambios de diseño: developer.android.com/training/animation/layout.html
ccpizza
No funciona después de presionar el botón Atrás. ¿Alguna sugerencia?
Hassan Tareq
44
Esto funciona perfectamente para expandir la animación, pero para el colapso, la animación tiene lugar después de que el diseño principal se reduce.
shine_joseph
3
@shine_joseph, sí, estoy usando esto dentro de una vista de reciclaje y cuando colapsar se ve realmente extraño: /
AmirG
65

Tomé la solución de @LenaYan que no me funcionó correctamente ( porque estaba transformando la Vista a una vista de altura 0 antes de colapsar y / o expandirse ) e hice algunos cambios.

Ahora funciona muy bien , tomando la altura anterior de la Vista y comienza a expandirse con este tamaño. Colapsar es lo mismo.

Simplemente puede copiar y pegar el siguiente código:

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

Uso:

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

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

¡Suficientemente fácil!

Gracias LenaYan por el código inicial!

Geraldo Neto
fuente
Aunque funciona, depende de la configuración del desarrollador (duración de la animación). Si está deshabilitado, no se mostrará ninguna animación.
CoolMind
Sí, pero puede o no ser un problema. Depende de su aplicación. Podría, por ejemplo, hacer que la duración de la animación sea proporcional al tamaño expandido / contraído con cambios simples. Tener una duración de animación configurable te da un poco más de libertad.
Geraldo Neto
Expandir animación no funciona. Parece una animación de colapso.
Ahamadullah Saikat
39

Una alternativa es utilizar una animación de escala con los siguientes factores de escala para expandirse:

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

y para colapsar:

ScaleAnimation anim = new ScaleAnimation(1, 1, 1, 0);
ChristophK
fuente
cómo iniciar la animación .. View.startAnimation (anim); no parece funcionar
Mahendran
eso es exactamente cómo comienzo la animación. ¿Otras animaciones funcionan para ti?
ChristophK
1
Con este enfoque, funciona de maravilla y no es necesario implementar lo que ya se ha implementado.
erbsman
15
Esto no empuja hacia abajo las vistas debajo de él durante la animación y parece que está estirando la vista animada de 0 -> h.
55
Por cierto, las animaciones de vista funcionan muy bien para escalar: oView.animate (). ScaleY (0) para colapsar verticalmente; oView.animate (). scaleY (1) para abrir (tenga en cuenta que solo está disponible sdk 12 y superior).
Kirk B.
27

La respuesta de @Tom Esterez , pero actualizada para usar view.measure () correctamente según Android getMeasuredHeight, devuelve valores incorrectos.

    // 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);
    }
Erik B
fuente
1
¿Qué es addHeight y DURATION_MULTIPLIER?
MidasLefko
Olvidó esos, addHeight es en caso de que necesite altura adicional en su expansión (probablemente no) y DURATION_MODIFIER es solo un modificador de velocidad en caso de que desee acelerar / ralentizar las animaciones.
Erik B
1
¡Funciona genial! Se produce un pequeño retraso al usar TextView con solo una palabra en la última línea. ¿Y podría explicar qué hace el PathInterpolator ...?
Yennsarah
La facilidadInOutQuart hace que la animación sea lenta al principio, luego rápida, luego lenta al final para una sensación muy natural. Hablan sobre esto en profundidad aquí easings.net
Erik B
1
Probé su método, pero cada vez que termina la animación, mi vista ya no es visible.
Aman Verma
26

Ok, acabo de encontrar una solución MUY fea:

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

¡No dude en proponer una mejor solución!

Tom Esterez
fuente
3
+1, incluso esto se llama feo, funciona para una vista en la que aún no sabemos su tamaño (por ejemplo, en caso de que agreguemos una vista recién creada (cuyo tamaño es FILL_PARENT) al padre y nos gustaría animar este proceso, incluida la animación del crecimiento del tamaño de los padres).
Vit Khudenko
Por cierto, parece que hay un pequeño error en la View.onMeause(widthMeasureSpec, heightMeasureSpec)invocación, por lo que las especificaciones de ancho y alto deben intercambiarse.
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();
}
LenaYan
fuente
1
Tengo este problema ... el contenido dentro de la vista plegable está desapareciendo en la expansión. Tengo Recycler View que desaparece al expandir esta vista. @LenaYan
Akshay Mahajan
21

Si no desea expandirse o contraerse completamente, aquí hay una simple Animación de altura:

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

Uso:

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

He adaptado la respuesta aceptada actualmente por Tom Esterez , que trabajó pero tenía un entrecortado y no muy suave animación. Mi solución básicamente reemplaza la Animationcon a ValueAnimator, que puede ajustarse con una Interpolatorde su elección para lograr varios efectos como sobreimpulso, rebote, aceleración, etc.

Esta solución funciona muy bien con vistas que tienen una altura dinámica (es decir, usando WRAP_CONTENT), ya que primero mide la altura real requerida y luego la anima a esa altura.

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

Entonces simplemente llama expand( myView );o collapse( myView );.

Magnus W
fuente
Gracias. También puede agregar una situación en la que la altura mínima no sea 0.
CoolMind
yo trabajo para mí para linearlayout
Roger
Simplemente corrigió los parámetros utilizados v.measure()y ahora está funcionando perfectamente. ¡Gracias!
Shahood ul Hassan
9

Haciendo uso de las funciones de extensión de Kotlin, esto es probado y la respuesta más corta

Simplemente llame a animateVisibility (expand / collapse) en cualquier vista.

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()
}
Rajkiran
fuente
quería publicar la misma respuesta :) Lástima que esto esté enterrado tan profundo aquí.
muetzenflo
@muetzenflo Si más y más personas votan la respuesta, aparecerá. :)
Rajkiran
Me gustó esta solución hasta que me di cuenta de que si hay una vista de texto con varias líneas con una altura de wrap_content, cuando se expande, la vista de texto solo mostrará una línea. Estoy tratando de arreglarlo ahora
olearyj234
Intenté esto, pero la animación no parece ser suave. Para expandir, toda la vista de texto aparece a la vez brevemente y luego se reproduce la animación. Para el colapso, la vista de texto se expande inmediatamente inmediatamente después del colapso, por alguna razón. ¿Alguna idea de lo que estoy haciendo mal?
Anchith Acharya
7

Agregando a la excelente respuesta de Tom Esterez y la excelente actualización de Erik B , pensé en publicar mi propia toma, compactando los métodos de expansión y contrato en uno. De esta manera, podría tener, por ejemplo, una acción como esta ...

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

... que llama al siguiente método y deja que descubra qué hacer después de cada 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);

}
mjp66
fuente
Intenté este código, pero para que funcione en varias vistas, debe desplazarse. ¿Alguna idea de cómo puedo solucionar esto? stackoverflow.com/q/43916369/1009507
sammyukavi
@Ukavi Estoy usando esto con múltiples vistas y funciona bien dentro de un ScrollView.
mjp66
¿Qué pasa en una vista de reciclaje?
sammyukavi
@Ukavi aún no ha tenido la necesidad de usarlo en una vista de reciclador, pero no puedo ver por qué no funcionaría. Tendrás que experimentar un poco tú mismo;)
mjp66
6

Me gustaría agregar algo a la muy útil respuesta anterior . Si no conoce la altura con la que terminará dado que sus vistas .getHeight () devuelve 0, puede hacer lo siguiente para obtener la altura:

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

Donde DUMMY_HIGH_DIMENSIONS es el ancho / alto (en píxeles) que su vista está restringida a ... tener un número enorme es razonable cuando la vista se encapsula con un ScrollView.

Gardarh
fuente
6

Este es un fragmento que utilicé para cambiar el tamaño del ancho de una vista (LinearLayout) con animación.

Se supone que el código se expande o reduce según el tamaño de destino. Si desea un ancho fill_parent, tendrá que pasar el ancho primario .getMeasuredWidth como ancho objetivo mientras establece el indicador en verdadero.

Espero que ayude a algunos de ustedes.

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

}

Codewarrior
fuente
¿Hay algún truco para que esto funcione? La clase obtiene los anchos originales y de destino correctos, pero mis vistas no cambiarán de tamaño. Estoy usando resizeAnim.start(). También he intentado con y sinsetFillAfter(true)
Ben Kane
Entendido. Tuve que recurrir .startAnimation(resizeAnim)a la vista.
Ben Kane
6

Para una animación suave, utilice el controlador con el método de ejecución ..... y disfrute de la animación Expandir / Contraer

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

Y llame usando este código:

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

}

Otra solución es:

               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);
}
Ashish Saini
fuente
5

Soluciones combinadas de @Tom Esterez y @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);
Flujo
fuente
4

Sí, acepté los comentarios anteriores. Y, de hecho, parece que lo correcto (¿o al menos lo más fácil?) Es especificar (en XML) una altura de diseño inicial de "0px", y luego puede pasar otro argumento para "toHeight" ( es decir, la "altura final") para el constructor de su subclase de animación personalizada, por ejemplo, en el ejemplo anterior, se vería algo así:

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

De todos modos, ¡espero que ayude! :)

Daniel Kopyc
fuente
4

Aquí está mi solución. Pienso que es más simple. Solo expande la vista, pero puede ampliarse fácilmente.

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;
    }
}
Kaloyan Donev
fuente
4

Creo que la solución más fácil es establecer android:animateLayoutChanges="true"a su LinearLayouty luego simplemente mostrar / ocultar la vista por proponiéndose su visibilidad. Funciona de maravilla, pero no tienes control sobre la duración de la animación.

Jacek Kwiecień
fuente
3

Estás en el camino correcto. Asegúrese de tener v1 configurado para tener una altura de diseño de cero justo antes de que comience la animación. Desea inicializar su configuración para que se vea como el primer fotograma de la animación antes de comenzar la animación.

Micah Hainline
fuente
Estoy de acuerdo, pero ¿cómo obtener la altura inicial (requerida por mi animación) si hago esto?
Tom Esterez
¿Ha intentado realmente guardar la altura inicial en initialize, establecer la vista visible allí y luego establecer v.getLayoutParams (). Height = 0; directamente después, todo en inicializar?
Micah Hainline
Sí, si lo hago, el método de inicialización se llama con height = 0
Tom Esterez
3

Este fue mi solución, mi ImageViewcrece de 100%a 200%y volver a su tamaño original, utilizando dos archivos de animación dentro de res/anim/la carpeta

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>

Enviar un ImageViewa mi métodosetAnimationGrowShrink()

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

setAnimationGrowShrink() método:

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

}
Jorgesys
fuente
3

Esta es una solución de trabajo adecuada, lo he probado:

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

Colapso:

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

Animador de valor:

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 vista v es la vista que se va a animar, PARENT_VIEW es la vista del contenedor que contiene la vista.

Anubhav
fuente
2

Esto es realmente simple con droidQuery . Para comenzar, considere este diseño:

<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>

Podemos animar la altura al valor deseado, digamos 100dp, usando el siguiente código:

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

Luego, usa droidQuerypara animar. La forma más simple es con esto:

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

Para hacer que la animación sea más atractiva, considere agregar una relajación:

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

También puede cambiar la duración al AnimationOptionsusar el duration()método o controlar lo que sucede cuando finaliza la animación. Para un ejemplo complejo, intente:

$.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);
                                                                 }
                                                             }));
Phil
fuente
2

La mejor solución para expandir / contraer vistas:

    @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();
    }
Владислав Стариков
fuente
Aunque funciona, también depende de la configuración del desarrollador (duración de la animación). Y pulir su código, eliminar la función lambda y formatear onCheckedChanged.
CoolMind
¿Por qué es suficiente llamar a requestLayout solo en v después de cambiar los LayoutParams de v? Pensé que sería necesario llamar a requestLayout en el padre de v
vlazzle
2

Puede usar un ViewPropertyAnimator con un ligero giro. Para colapsar, escala la vista a una altura de 1 píxel, luego escóndela. Para expandir, muéstrelo, luego amplíelo a su altura.

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

El pivote le dice a la vista desde dónde escalar, el valor predeterminado está en el medio. La duración es opcional (por defecto = 1000). También puede configurar el interpolador para usar, como.setInterpolator(new AccelerateDecelerateInterpolator())

Alegría
fuente
1

Creé una versión en la que no necesita especificar la altura del diseño, por lo tanto, es mucho más fácil y limpio de usar. La solución es obtener la altura en el primer fotograma de la animación (está disponible en ese momento, al menos durante mis pruebas). De esta manera, puede proporcionar una Vista con una altura arbitraria y un margen inferior.

También hay un pequeño truco en el constructor: el margen inferior se establece en -10000 para que la vista permanezca oculta antes de la transformación (evita el parpadeo).

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);
    }
}
Michał K
fuente
1

Use 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();
Jon
fuente
En mi caso no hace nada. También puede facilitar su código, contrayendo 2 líneas 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);
queroseno
fuente
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 clase se puede llamar de la siguiente manera

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

Basado en soluciones de @Tom Esterez y @Seth Nelson (top 2) las simplifiqué. Además de las soluciones originales, no depende de las opciones de desarrollador (configuración de animación).

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);
}
CoolMind
fuente
Bueno, después de 3 años probé nuevamente varias soluciones, pero solo la mía funcionó correctamente.
CoolMind