Android: expandir / recolher animação

449

Digamos que eu tenho um linearLayout vertical com:

[v1]
[v2]

Por padrão, a v1 tem visibily = GONE. Gostaria de mostrar a v1 com uma animação de expansão e pressionar a v2 ao mesmo tempo.

Eu tentei algo assim:

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

Mas com esta solução, eu pisquei quando a animação é iniciada. Eu acho que é causado pela v1 exibindo o tamanho total antes da animação ser aplicada.

Com javascript, esta é uma linha do jQuery! Alguma maneira simples de fazer isso com o Android?

Tom Esterez
fonte

Respostas:

734

Vejo que essa pergunta se tornou popular, então eu posto minha solução real. A principal vantagem é que você não precisa conhecer a altura expandida para aplicar a animação e, uma vez que a exibição é expandida, ela adapta a altura se o conteúdo for alterado. Funciona muito bem para mim.

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

Conforme mencionado por @Jefferson nos comentários, você pode obter uma animação mais suave alterando a duração (e, portanto, a velocidade) da animação. Atualmente, foi definido a uma velocidade de 1dp / ms

Tom Esterez
fonte
13
v.measure (MeasureSpec.makeMeasureSpec (LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec (LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); Em alguns casos (o meu - ListView) Esse desencontro leva a valor targtetHeight errado
Johnny Doe
12
@ Tom Esterez Isso funciona, mas não muito bem. Existe algum trabalho adicional para torná-lo sem problemas?
acntwww
9
@acntwww Você pode obter uma animação suave multiplicando a duração por algum fator, como 4.a.setDuration(((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density)) * 4)
Jefferson Henrique C. Soares
10
@Alioo, importe android.view.animation.Transformation;
Jomia
5
Funciona bem! Eu tive problemas com a altura medida, pois queria expandir um elemento dp fixo, então mudei a medida para v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));e v.getLayoutParams().height = interpolatedTime == 1 ? targetHeight : (int)(targetHeight * interpolatedTime);Isso funcionou para mim!
precisa saber é o seguinte
140

Eu estava tentando fazer o que acredito ser uma animação muito semelhante e encontrei uma solução elegante. Este código pressupõe que você esteja sempre indo de 0-> h ou h-> 0 (h sendo a altura máxima). Os três parâmetros do construtor são view = a view a ser animada (no meu caso, uma webview), targetHeight = a altura máxima da view e down = um booleano que especifica a direção (true = expandindo, false = recolhendo).

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
fonte
5
Há um erro de digitação no código: o nome do método "inicializar" deve ser "inicializar" ou não será chamado. ;) Eu recomendaria usar o @Override no futuro, para que esse tipo de erro seja detectado pelo compilador.
Lorne Laliberte
4
Estou fazendo o seguinte: "DropDownAnim anim = new DropDownAnim (grid_titulos_atual, GRID_HEIGHT, true); anim.setDuration (500); anim.start ();" mas não está funcionando. Eu coloquei alguns pontos de interrupção no applyTransformation mas eles nunca estão sendo alcançados
Paulo Cesar
5
Ops, tenho que trabalhar, é view.startAnimation (a) ... O desempenho não é muito bom, mas funciona :)
Paulo Cesar
3
@IamStalker Nessa situação, você provavelmente deve inicializar com duas variáveis, começando com Altura e terminando com Altura. Em seguida, mude para: if (down) {newHeight = (int) (((endingHeight-inícioHeight) * interpolatedTime) + StartingHeight); } else {newHeight = (int) (((endingHeight-StartingHeight) * (1 - tempo interpolado)) + StartingHeight); }
Seth Nelson
3
@Seth Eu acho que newHeight pode simplesmente ser (int) (((targetHeight -startingHeight) * interpolatedTime) + StartingHeight), não importa a direção, desde que o startupHeight seja definido em initialize ().
Giorgos Kylafas
138

Tropecei no mesmo problema hoje e acho que a verdadeira solução para essa pergunta é essa

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

Você precisará definir essa propriedade para todos os layouts superiores, envolvidos no turno. Se você agora definir a visibilidade de um layout como GONE, o outro ocupará o espaço à medida que o desaparecido o estiver liberando. Haverá uma animação padrão que é algum tipo de "desaparecimento", mas acho que você pode mudar isso - mas a última que ainda não testei por enquanto.

Mr.Fu
fonte
2
+1, Agora estou pesquisando Velocidade: duração do animateLayoutChanges
Tushar Pandey
9
Animando layout Mudanças: developer.android.com/training/animation/layout.html
ccpizza
Não funciona depois de pressionar o botão Voltar. Alguma sugestão?
precisa saber é o seguinte
4
Isso funciona perfeitamente para expandir a animação, mas para recolher a animação ocorre depois que o layout principal é reduzido.
shine_joseph
3
@shine_joseph sim eu estou usando isso dentro de um recyclerview e quando o colapso parece realmente estranho: /
AmirG
65

Peguei a solução da @LenaYan que não funcionou corretamente para mim ( porque estava transformando a vista em uma vista de altura 0 antes de recolher e / ou expandir ) e fiz algumas alterações.

Agora funciona muito bem , tomando a altura anterior do View e começando a expandir com esse tamanho. Desmoronar é o mesmo.

Você pode simplesmente copiar e colar o código abaixo:

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

Bastante fácil!

Obrigado LenaYan pelo código inicial!

Geraldo Neto
fonte
Embora funcione, depende das configurações do desenvolvedor (duração da animação). Se estiver desativado, nenhuma animação será mostrada.
CoolMind 29/07/16
Sim, mas pode ou não ser um problema. Depende da sua aplicação. Você poderia, por exemplo, facilmente tornar a duração da animação proporcional ao tamanho expandido / recolhido com alterações simples. Ter uma duração de animação configurável oferece um pouco mais de liberdade.
Geraldo Neto
A animação de expansão não está funcionando. parece animação de colapso.
Ahamadullah Saikat
39

Uma alternativa é usar uma animação em escala com os seguintes fatores de escala para expansão:

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

e para desmoronar:

ScaleAnimation anim = new ScaleAnimation(1, 1, 1, 0);
ChristophK
fonte
como iniciar a animação .. View.startAnimation (anim); não parece funcionar
Mahendran
é exatamente assim que começo a animação. Outras animações funcionam para você?
ChristophK
1
Fui com essa abordagem, funciona como um encanto e não há necessidade de implementar o que já foi implementado.
31512 erbsman
15
Isso não empurra as vistas abaixo dela durante a animação e parece que está esticando a vista animada de 0 -> h.
5
Aliás, as animações de exibição funcionam muito bem para dimensionar: oView.animate (). ScaleY (0) para recolher verticalmente; oView.animate (). scaleY (1) para abrir (observe que ele está disponível apenas no SDK 12 ou superior).
Kirk B.
27

A resposta de Tom Esterez , mas atualizada para usar view.measure () corretamente de acordo com o Android, getMeasuredHeight retorna valores errados!

    // 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
fonte
1
o que é addHeight e DURATION_MULTIPLIER?
MidasLefko
Esqueceu-se disso, addHeight é o caso de você precisar de uma altura extra na sua expansão (provavelmente não) e DURATION_MODIFIER é apenas um modificador de velocidade, caso você queira acelerar / diminuir as animações.
Erik B
1
Funciona bem! Um pequeno atraso ocorre ao usar o TextView com apenas uma palavra na última linha. E você poderia explicar o que o PathInterpolator faz ..?
yennsarah
A facilidadeInOutQuart torna a animação lenta no início, depois rápida e depois lenta no final, para uma sensação muito natural. Eles falam sobre isso em profundidade aqui easings.net
Erik B
1
Eu tentei o seu método, mas sempre que a animação termina, minha visualização não é mais visível.
Aman Verma
26

Ok, acabei de encontrar uma solução MUITO feia:

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

Sinta-se livre para propor uma solução melhor!

Tom Esterez
fonte
3
+1, mesmo que seja chamado de feio, ele funciona para uma visualização em que ainda não sabemos seu tamanho (por exemplo, no caso de adicionarmos uma visualização recém-criada (cujo tamanho é FILL_PARENT) ao pai e gostaria de animar esse processo, inclusive animando o crescimento do tamanho do pai).
precisa saber é o seguinte
BTW, parece que há um pequeno erro na View.onMeause(widthMeasureSpec, heightMeasureSpec)chamada, portanto as especificações de largura e altura devem ser trocadas.
precisa saber é o seguinte
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
fonte
1
Eu tenho esse problema ... o conteúdo dentro da exibição recolhível está desaparecendo na expansão. Eu tenho o Recycler View, que desaparece ao expandir esse modo de exibição. @LenaYan
Akshay Mahajan
21

Se você não deseja expandir ou recolher todo o caminho - eis uma simples 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;
    }
}

Uso:

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

Adaptei a resposta atualmente aceita por Tom Esterez , que funcionou, mas tinha uma animação irregular e não muito suave. Minha solução substitui basicamente o Animationcom a ValueAnimator, que pode ser equipado com um Interpolatorde sua escolha para obter vários efeitos, como ultrapassagem, rejeição, aceleração etc.

Esta solução funciona muito bem com vistas que têm uma altura dinâmica (ou seja, usando WRAP_CONTENT), pois primeiro mede a altura real necessária e depois anima para essa 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();
}

Você simplesmente chama expand( myView );ou collapse( myView );.

Magnus W
fonte
Obrigado. Você também pode adicionar uma situação quando a altura mínima não é 0.
CoolMind
eu trabalho para mim para linearlayout
Roger
Apenas corrigimos os parâmetros usados v.measure()e agora ele está funcionando perfeitamente. Obrigado!
Shahood ul Hassan
9

Fazendo uso das funções de extensão Kotlin, isso é testado e a resposta mais curta

Basta chamar animateVisibility (expandir / recolher) em qualquer Visualização.

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
fonte
queria postar a mesma resposta :) Pena que isso seja enterrado tão fundo aqui.
muetzenflo
@muetzenflo Se cada vez mais pessoas votarem na resposta, ela surgirá. :)
Rajkiran
Eu gostei dessa solução até perceber que, se houver uma visualização de texto com várias linhas com uma altura de wrap_content, quando expandida, a visualização de texto mostrará apenas uma linha. Eu estou tentando consertar agora #
olearyj234 18/10/19
Eu tentei isso, mas a animação não parece ser suave. Para expandir, a visualização de texto inteira aparece rapidamente e depois a animação é reproduzida. Para recolher, a exibição de texto se expande imediatamente imediatamente após o recolhimento, por algum motivo. Alguma idéia do que estou fazendo de errado?
Anchith Acharya
7

Adicionando a excelente resposta de Tom Esterez e a excelente atualização de Erik B , pensei em publicar minha própria opinião, compactando os métodos de expansão e contrato em um. Dessa forma, você poderia, por exemplo, ter uma ação como esta ...

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

... que chama o método abaixo e permite descobrir o que fazer depois 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
fonte
Eu tentei esse código, mas para ele funcionar em vários modos de exibição, você precisa rolar. Alguma idéia de como eu posso consertar isso? stackoverflow.com/q/43916369/1009507
sammyukavi
@Ukavi Estou usando isso com várias visualizações e funciona bem dentro de um ScrollView.
Mjp66
Que tal em uma revisão de reciclagem?
Sammyukavi
O @Ukavi ainda não precisou usá-lo em uma revisão de reciclagem, mas não vejo por que não funcionaria. Você terá que experimentar com ele-se um pouco;)
mjp66
6

Gostaria de acrescentar algo à resposta muito útil acima . Se você não souber a altura com a qual você terminará desde que suas visualizações .getHeight () retornem 0, faça o seguinte para obter a altura:

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

Onde DUMMY_HIGH_DIMENSIONS é a largura / altura (em pixels) que sua visualização é restrita a ... ter um número enorme é razoável quando a visualização é encapsulada com um ScrollView.

Gardarh
fonte
6

Este é um trecho que eu usei para redimensionar a largura de uma exibição (LinearLayout) com animação.

O código deve expandir ou diminuir de acordo com o tamanho do destino. Se você deseja uma largura de fill_parent, precisará passar o pai .getMeasuredWidth como largura de destino enquanto define o sinalizador como true.

Espero que ajude alguns de vocês.

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
fonte
Existe algum truque para fazer isso funcionar? A classe obtém as larguras originais e de destino corretas, mas minhas visualizações não serão redimensionadas. Estou usando resizeAnim.start(). Também tentei com e semsetFillAfter(true)
Ben Kane
Entendi. Teve que chamar .startAnimation(resizeAnim)a vista.
Ben Kane
6

Para animação suave, use Handler com o método run ..... E aproveite a animação Expandir / Recolher

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

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

}

Outra solução é:

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

soluções combinadas de @Tom Esterez e @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);
Fluxo
fonte
4

Sim, concordei com os comentários acima. E, de fato, parece que a coisa certa (ou pelo menos a mais fácil?) A fazer é especificar (em XML) uma altura de layout inicial de "0px" - e então você pode passar outro argumento para "toHeight" ( por exemplo, a "altura final") para o construtor da sua subclasse de animação personalizada, por exemplo, no exemplo acima, seria algo como:

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

Enfim, espero que ajude! :)

Daniel Kopyc
fonte
4

Aqui está a minha solução. Eu acho que é mais simples. Ele apenas expande a visualização, mas pode ser facilmente estendido.

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

Penso que a solução mais fácil é definir android:animateLayoutChanges="true"a sua LinearLayoute, em seguida, apenas mostrar / ocultar a visualização, definindo sua visibilidade. Funciona como um encanto, mas você não tem controle sobre a duração da animação

Jacek Kwiecień
fonte
3

Você está no caminho certo. Verifique se a v1 está configurada para ter uma altura de layout igual a zero antes do início da animação. Você deseja inicializar sua configuração para se parecer com o primeiro quadro da animação antes de iniciar a animação.

Micah Hainline
fonte
Concordo, mas como obter a altura inicial (exigida pela minha animação) se eu fizer isso?
Tom Esterez
Você já tentou salvar a altura inicial na inicialização, definindo a visualização como visível lá e definindo v.getLayoutParams (). Height = 0; diretamente depois, tudo na inicialização?
Micah Hainline
Sim, se eu fizer assim o método initialize é chamado com height = 0
Tom Esterez
3

Esta foi a minha solução, meu ImageViewcresce a partir 100%de 200%e regresso ao seu tamanho original, utilizando dois arquivos de animação dentro res/anim/da pasta

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 um ImageViewpara o meu 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
fonte
3

Esta é uma solução de trabalho adequada, eu testei:

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

Value Animator:

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

A vista v é a vista a ser animada, PARENT_VIEW é a vista do contentor que contém a vista.

Anubhav
fonte
2

Isso é realmente simples com o droidQuery . Para começar, considere este layout:

<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 a altura até o valor desejado - digamos 100dp- usando o seguinte código:

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

Em seguida, use droidQuerypara animar. A maneira mais simples é com isso:

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

Para tornar a animação mais atraente, considere adicionar um facilitador:

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

Você também pode alterar a duração AnimationOptionsusando o duration()método ou manipular o que acontece quando a animação termina. Para um exemplo complexo, tente:

$.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
fonte
2

Melhor solução para as visualizações de expansão / recolhimento:

    @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();
    }
Владислав Стариков
fonte
Embora funcione, também depende das configurações do desenvolvedor (duração da animação). E aperfeiçoe seu código, exclua a função lambda e reformate onCheckedChanged.
CoolMind 29/07
Por que é suficiente chamar requestLayout apenas em v após alterar o LayoutParams de v? Eu acho que seria necessário chamar requestLayout no pai de v
vlazzle
2

Você pode usar um ViewPropertyAnimator com um leve toque. Para recolher, redimensione a vista para uma altura de 1 pixel e oculte-a. Para expandir, mostre-o e expanda-o à sua 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);
}

O pivô informa à vista de onde escalar, o padrão está no meio. A duração é opcional (padrão = 1000). Você também pode definir o interpolador para usar, como.setInterpolator(new AccelerateDecelerateInterpolator())

Alegria
fonte
1

Criei uma versão na qual você não precisa especificar a altura do layout, portanto, é muito mais fácil e limpo de usar. A solução é obter a altura no primeiro quadro da animação (ela está disponível naquele momento, pelo menos durante os meus testes). Dessa forma, você pode fornecer à View uma altura e margem inferiores arbitrárias.

Há também um pequeno hack no construtor - a margem inferior é definida como -10000, para que a exibição permaneça oculta antes da transformação (evita tremulações).

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
fonte
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
fonte
No meu caso não faz nada. Além disso, você pode facilitar seu código, recolhendo 2 linhas mainView.getLayoutParams().height = height.
CoolMind 29/07
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);
querosene
fonte
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;
    }
}

A classe pode ser chamada da seguinte maneira

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

Com base nas soluções de @Tom Esterez e @Seth Nelson (top 2), eu as simplifiquei. Além das soluções originais, não depende das opções do desenvolvedor (configurações de animação).

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
fonte
Bem, depois de 3 anos, testei novamente várias soluções, mas apenas a minha funcionou corretamente.
CoolMind