Android: Rozwiń / zwiń animację


449

Powiedzmy, że mam pionowy układ liniowy z:

[v1]
[v2]

Domyślnie v1 ma visibily = GONE. Chciałbym pokazać v1 z animacją rozwijania i jednocześnie nacisnąć v2.

Próbowałem czegoś takiego:

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

Ale dzięki temu rozwiązaniu mam mrugnięcie, gdy rozpoczyna się animacja. Myślę, że jest to spowodowane wyświetlaniem pełnego rozmiaru v1 przed zastosowaniem animacji.

W javascript jest to jedna linia jQuery! Jakiś prosty sposób to zrobić na Androidzie?

Odpowiedzi:


734

Widzę, że to pytanie stało się popularne, dlatego opublikowałem swoje aktualne rozwiązanie. Główną zaletą jest to, że nie trzeba znać powiększonej wysokości, aby zastosować animację, a po rozwinięciu widoku dostosowuje wysokość w przypadku zmiany treści. Działa świetnie dla mnie.

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

Jak wspomniano w komentarzach @Jefferson, można uzyskać płynniejszą animację, zmieniając czas trwania (a tym samym szybkość) animacji. Obecnie jest ustawiony na prędkość 1dp / ms


13
v.measure (MeasureSpec.makeMeasureSpec (LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec (LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY); W niektórych przypadkach (my - ListView) to niedopasowanie prowadzi do błędnej wartości targettetHeight
Johnny Doe

12
@Tom Esterez To działa, ale niezbyt płynnie. Czy są jakieś dodatkowe prace, aby sprawnie to zrobić?
acntwww

9
@acntwww Możesz uzyskać płynną animację pomnożącą czas trwania przez pewien czynnik, np. 4.a.setDuration(((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density)) * 4)
Jefferson Henrique C. Soares

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

5
Działa świetnie! Miałem problemy z mierzoną wysokością, ponieważ chciałem rozszerzyć stały element dp, więc zmieniłem miarę na v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));i v.getLayoutParams().height = interpolatedTime == 1 ? targetHeight : (int)(targetHeight * interpolatedTime);To działało dla mnie!
vkislicins

140

Próbowałem zrobić coś, co według mnie było bardzo podobną animacją, i znalazłem eleganckie rozwiązanie. Ten kod zakłada, że ​​zawsze zaczynasz od 0-> h lub h-> 0 (h to maksymalna wysokość). Trzy parametry konstruktora to view = widok do animacji (w moim przypadku webview), targetHeight = maksymalna wysokość widoku i down = wartość logiczna określająca kierunek (true = rozwijanie, false = zwijanie).

public class DropDownAnim extends Animation {
    private final int targetHeight;
    private final View view;
    private final boolean down;

    public DropDownAnim(View view, int targetHeight, boolean down) {
        this.view = view;
        this.targetHeight = targetHeight;
        this.down = down;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        int newHeight;
        if (down) {
            newHeight = (int) (targetHeight * interpolatedTime);
        } else {
            newHeight = (int) (targetHeight * (1 - interpolatedTime));
        }
        view.getLayoutParams().height = newHeight;
        view.requestLayout();
    }

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

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

5
W kodzie jest literówka: nazwa metody „initalize” powinna być „initialize”, inaczej nie zostanie wywołana. ;) Polecam użycie @Override w przyszłości, aby ten typ literówki został złapany przez kompilator.
Lorne Laliberte

4
Wykonuję następujące czynności: „DropDownAnim anim = new DropDownAnim (grid_titulos_atual, GRID_HEIGHT, true); anim.setDuration (500); anim.start ();” ale to nie działa. Umieściłem niektóre punkty przerwania na ApplyTransformation, ale nigdy nie są osiągane
Paulo Cesar

5
Operacje, sprawiło, że działało, to view.startAnimation (a) ... Wydajność nie jest zbyt dobra, ale działa :)
Paulo Cesar

3
@IamStalker W takiej sytuacji prawdopodobnie powinieneś zainicjować dwie zmienne, zaczynającHeight i kończącHeight. Następnie zmień na: if (dół) {newHeight = (int) (((endingHeight-startHeight) * interpolatedTime) + startHeight); } else {newHeight = (int) (((endingHeight-startHeight) * (1 - interpolowany czas)) + startHeight); }
Seth Nelson

3
@Seth Myślę, że newHeight może być po prostu (int) (((targetHeight -startingHeight) * interpolatedTime) + startHeight), bez względu na kierunek, pod warunkiem, że startHeight jest ustawiony w initialize ().
Giorgos Kylafas,

138

Natknąłem się dzisiaj na ten sam problem i wydaje mi się, że prawdziwym rozwiązaniem tego pytania jest to

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

Musisz ustawić tę właściwość dla wszystkich najwyższych układów, które biorą udział w zmianie. Jeśli teraz ustawisz widoczność jednego układu na BRAK, drugi zajmie miejsce, gdy zniknie jeden z nich. Będzie domyślna animacja, która jest pewnego rodzaju „zanikaniem”, ale myślę, że możesz to zmienić - ale ostatnią, której na razie nie testowałem.


2
+1, Teraz szukam Speed: czas trwania animateLayoutChanges
Tushar Pandey

9
Animowanie zmian w
układzie

Nie działa po naciśnięciu przycisku Wstecz. Jakieś sugestie?
Hassan Tareq

4
Działa to doskonale w przypadku animacji rozwijanej, ale w przypadku zwinięcia animacja ma miejsce po zmniejszeniu układu nadrzędnego.
shine_joseph

3
@shine_joseph yeah, używam tego w widoku recyclingu, a kiedy zapadanie się wygląda naprawdę dziwnie: /
AmirG

65

Wziąłem rozwiązanie @LenaYan, które nie działało dla mnie poprawnie ( ponieważ przekształcało widok do widoku o wysokości 0 przed zwinięciem i / lub rozwinięciem ) i wprowadziłem pewne zmiany.

Teraz działa świetnie , biorąc poprzednią wysokość widoku i zacznij powiększać z tym rozmiarem. Składanie jest takie samo.

Możesz po prostu skopiować i wkleić poniższy kod:

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

Stosowanie:

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

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

Wystarczająco łatwe!

Dzięki LenaYan za kod początkowy!


Chociaż działa, zależy od ustawień programisty (czas trwania animacji). Jeśli jest wyłączone, animacja nie będzie wyświetlana.
CoolMind,

Tak, ale może to stanowić problem. Zależy od twojej aplikacji. Można na przykład łatwo ustawić czas trwania animacji proporcjonalnie do powiększonego / zwiniętego rozmiaru za pomocą prostych zmian. Ustawiony czas trwania animacji daje nieco więcej swobody.
Geraldo Neto

Rozwiń animację nie działa. wygląda jak animacja zwinięcia.
Ahamadullah Saikat

39

Alternatywą jest użycie animacji skali z następującymi czynnikami skalowania do rozwijania:

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

i do zwijania:

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

jak rozpocząć animację .. View.startAnimation (anim); nie wydaje się działać
Mahendran

właśnie w ten sposób rozpoczynam animację. Czy inne animacje działają dla Ciebie?
ChristophK

1
Poszedł z tym podejściem, działa jak urok i nie trzeba wdrażać tego, co już zostało wdrożone.
erbsman

15
To nie przesuwa widoków pod nim podczas animacji i wygląda na rozciąganie animowanego widoku od 0 -> h.

5
Przy okazji, animacje widoku świetnie nadają się do skalowania: oView.animate (). ScaleY (0), aby zwinąć się w pionie; oView.animate (). scaleY (1), aby otworzyć (pamiętaj, że jest dostępny tylko od sdk 12 i więcej).
Kirk B.

27

@Tom Esterez za odpowiedź , ale zaktualizowany do korzystania view.measure () właściwie za Android getMeasuredHeight zwraca błędne wartości!

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

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

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

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

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

            view.requestLayout();
        }

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

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

        return animation;
    }

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

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

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

        a.setInterpolator(easeInOutQuart);

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

        view.startAnimation(a);

        return a;
    }

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

1
co to jest addHeight i DURATION_MULTIPLIER?
MidasLefko

Zapomniałem o tym, addHeight jest na wypadek, gdybyś potrzebował dodatkowej wysokości w rozwinięciu (prawdopodobnie nie), a DURATION_MODIFIER to tylko modyfikator prędkości na wypadek, gdybyś chciał przyspieszyć / spowolnić animacje.
Erik B,

1
Działa świetnie! Podczas używania TextView występuje tylko jedno małe opóźnienie z jednym słowem w ostatnim wierszu. Czy możesz wyjaśnić, co robi PathInterpolator ..?
yennsarah

Dzięki easyInOutQuart animacja jest początkowo wolna, potem szybka, a następnie wolna na końcu, co daje bardzo naturalne wrażenie. Mówią o tym dogłębnie tutaj easings.net
Erik B

1
wypróbowałem twoją metodę, ale gdy kończy się animacja, mój widok nie jest już widoczny.
Aman Verma

26

Ok, właśnie znalazłem BARDZO brzydkie rozwiązanie:

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

Zapraszam do zaproponowania lepszego rozwiązania!


3
+1, nawet ten jest nazywany brzydkim, działa w widoku, w którym nie znamy jeszcze jego rozmiaru (np. W przypadku dodania nowo utworzonego widoku (którego rozmiar to FILL_PARENT) do rodzica i chciałby animować ten proces, w tym animowanie wzrostu wielkości rodzica).
Vit Khudenko

BTW, wygląda na to, że w View.onMeause(widthMeasureSpec, heightMeasureSpec)wywołaniu jest mały błąd , więc specyfikacje szerokości i wysokości powinny zostać zamienione.
Vit Khudenko

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

1
Mam ten problem ... zawartość wewnątrz zwijanego widoku znika po rozwinięciu. Mam Recycler View, który znika po rozwinięciu tego widoku. @LenaYan
Akshay Mahajan

21

Jeśli nie chcesz rozwinąć ani zwinąć do końca - oto prosta wysokośćAnimacja -

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

Stosowanie:

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

13

Dostosowałem obecnie akceptowaną odpowiedź Toma Estereza , który działał, ale miał niespokojną i niezbyt płynną animację. Moje rozwiązanie w zasadzie zastępuje literę „ Animationa” ValueAnimator, którą można wyposażyć Interpolatorw dowolne wybrane ustawienie, aby osiągnąć różne efekty, takie jak przeregulowanie, odbicie, przyspieszenie itp.

To rozwiązanie świetnie sprawdza się w widokach o dynamicznej wysokości (tj. Przy użyciu WRAP_CONTENT), ponieważ najpierw mierzy rzeczywistą wymaganą wysokość, a następnie animuje do tej wysokości.

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

Następnie wystarczy zadzwonić expand( myView );lub collapse( myView );.


Dzięki. Możesz także dodać sytuację, gdy minimalna wysokość nie jest równa 0.
CoolMind

pracuję dla mnie dla liniowej rozgrywki
Roger

Właśnie poprawiłem parametry używane v.measure()i teraz działa idealnie. Dzięki!
Shahood ul Hassan

9

Korzystając z funkcji rozszerzających Kotlin jest to przetestowana i najkrótsza odpowiedź

Wystarczy wywołać funkcję animateVisibility (rozwinąć / zwinąć) w dowolnym widoku.

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

chciałem opublikować tę samą odpowiedź :) Szkoda, że ​​jest tu tak głęboko schowany.
muetzenflo

@muetzenflo Jeśli coraz więcej osób zagłosuje za odpowiedzią, pojawi się ona. :)
Rajkiran

Podobało mi się to rozwiązanie, dopóki nie zorientowałem się, że istnieje widok tekstu z wieloma wierszami o wysokości wrap_content, po rozwinięciu widok tekstu pokaże tylko jedną linię. Próbuję naprawić teraz
olearyj234,

Próbowałem tego, ale animacja nie wydaje się być płynna. W celu rozwinięcia cały widok tekstu pojawia się na krótko, a następnie odtwarzana jest animacja. W przypadku zwinięcia widok tekstu z jakiegoś powodu natychmiast ponownie się rozwija. Masz pojęcie, co robię źle?
Anchith Acharya

7

Dodając do doskonałej odpowiedzi Toma Estereza i doskonałej aktualizacji Erika B , pomyślałem, że opublikuję własne ujęcie, łącząc metody rozwijania i kontraktowania w jedną. W ten sposób możesz na przykład wykonać taką akcję ...

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

... który wywołuje poniższą metodę i pozwala jej dowiedzieć się, co zrobić po każdej operacji 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);

}

Próbowałem tego kodu, ale aby go holować na wielu widokach, musisz przewijać. Masz pomysł, jak to naprawić? stackoverflow.com/q/43916369/1009507
sammyukavi

@Ukavi Używam tego z wieloma widokami i działa dobrze w ScrollView.
mjp66

A co z widokiem z recyklingu?
sammyukavi

@Ukavi nie musiał jeszcze używać go w widoku recyklingu, ale nie rozumiem, dlaczego to nie działa. Będziesz musiał z tym trochę
poeksperymentować

6

Chciałbym dodać coś do bardzo pomocnej odpowiedzi powyżej . Jeśli nie znasz wysokości, na której się skończysz, ponieważ Twoje widoki .getHeight () zwraca 0, możesz wykonać następujące czynności, aby uzyskać wysokość:

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

Tam, gdzie DUMMY_HIGH_DIMENSIONS jest szerokością / wysokością (w pikselach), twój widok jest ograniczony do ... posiadanie tej ogromnej liczby jest rozsądne, gdy widok jest zamknięty w ScrollView.


6

Jest to fragment, którego użyłem do zmiany szerokości widoku (LinearLayout) z animacją.

Kod ma się rozszerzać lub zmniejszać zgodnie z wielkością docelową. Jeśli chcesz szerokość fill_parent, musisz podać rodzic .getMeasuredWidth jako szerokość docelową, ustawiając flagę na true.

Mam nadzieję, że to pomoże niektórym z was.

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

}


Czy jest jakiś sposób, żeby to zadziałało? Klasa otrzymuje prawidłowe szerokości oryginału i docelowego, ale moje widoki się nie zmieniają. Używam resizeAnim.start(). Próbowałem również zi bezsetFillAfter(true)
Ben Kane

Rozumiem. Musiałem zadzwonić .startAnimation(resizeAnim)na widok.
Ben Kane,

6

Aby uzyskać płynną animację, użyj modułu obsługi z metodą uruchamiania ..... I ciesz się animacją Rozwiń / Zwiń

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

I zadzwoń, używając tego kodu:

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

}

Inne rozwiązanie to:

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

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

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

        anim.setAnimationListener(collapselistener);
    }

     // To Collapse
        //

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

5

połączone rozwiązania @Tom Esterez i @Geraldo Neto

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

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

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

4

Tak, zgodziłem się z powyższymi komentarzami. I rzeczywiście wydaje się, że właściwą (a przynajmniej najłatwiejszą?) Rzeczą jest określenie (w XML) początkowej wysokości układu „0px” - a następnie można podać kolejny argument dla „toHeight” ( tj. „wysokość końcowa”) do konstruktora niestandardowej podklasy Animacji, np. w powyższym przykładzie wyglądałoby to tak:

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

W każdym razie, mam nadzieję, że to pomaga! :)


4

Oto moje rozwiązanie. Myślę, że to jest prostsze. To tylko rozszerza widok, ale można go łatwo rozszerzyć.

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

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

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

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

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

        _targetWidth = width;
    }

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

4

Myślę, że najprostszym rozwiązaniem jest zestaw android:animateLayoutChanges="true"do LinearLayouti po prostu pokazać / ukryć widzenia przez seting swoją widoczność. Działa jak urok, ale nie masz kontroli nad czasem trwania animacji


3

Jesteś na dobrej drodze. Upewnij się, że masz v1 ustawiony na zerową wysokość układu tuż przed rozpoczęciem animacji. Chcesz zainicjować konfigurację, aby wyglądała jak pierwsza klatka animacji przed rozpoczęciem animacji.


Zgadzam się, ale jak uzyskać początkową wysokość (wymaganą przez moją animację), jeśli to zrobię?
Tom Esterez,

Czy próbowałeś właśnie zapisać początkową wysokość podczas inicjalizacji, ustawić widok tam widoczny, a następnie ustawić v.getLayoutParams (). Height = 0; bezpośrednio po, wszystko jest inicjowane?
Micheasz Hainline

Tak, jeśli to zrobię, wywoływana jest metoda inicjalizacji o wysokości = 0
Tom Esterez

3

To było moje rozwiązanie, mój ImageViewrośnie od 100%celu 200%i wrócić do swojego pierwotnego rozmiaru, przy użyciu dwóch plików animacji wewnątrz res/anim/folderu

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>

Wyślij ImageViewdo mojej metodysetAnimationGrowShrink()

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

setAnimationGrowShrink() metoda:

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

    imgV.startAnimation(animationEnlarge);

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

        @Override
        public void onAnimationRepeat(Animation animation) {}

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

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

        @Override
        public void onAnimationRepeat(Animation animation) {}

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

}

3

To jest właściwe działające rozwiązanie, przetestowałem to:

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

Zawalić się:

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

Animator wartości:

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

Widok v to widok do animacji, PARENT_VIEW to widok kontenera zawierający widok.


2

To jest naprawdę proste dzięki droidQuery . Na początek rozważ ten układ:

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

Możemy animować wysokość do pożądanej wartości - powiedzmy 100dp- używając następującego kodu:

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

Następnie użyj droidQuerydo animacji. Najprostszy sposób to:

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

Aby animacja była bardziej atrakcyjna, rozważ dodanie łagodzenia:

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

Możesz także zmienić czas trwania przy AnimationOptionsużyciu tej duration()metody lub obsłużyć to, co się stanie po zakończeniu animacji. Dla złożonego przykładu spróbuj:

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

2

Najlepsze rozwiązanie dla widoków rozwijania / zwijania:

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

Chociaż działa, zależy również od ustawień programisty (czas trwania animacji). I wypoleruj swój kod, usuń funkcję lambda i sformatuj onCheckedChanged.
CoolMind,

Dlaczego wystarczy wywołać requestLayout tylko na v po zmianie v LayoutParams? Pomyślałem, że konieczne będzie wywołanie requestLayout na rodzicu v
vlazzle,

2

Możesz użyć ViewPropertyAnimator z lekkim zwrotem akcji. Aby zwinąć, przeskaluj widok do wysokości 1 piksela, a następnie ukryj go. Aby rozwinąć, pokaż go, a następnie rozwiń na wysokość.

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ś określa widok, z którego miejsca ma być skalowana, domyślnie jest na środku. Czas trwania jest opcjonalny (domyślnie = 1000). Możesz także ustawić interpolator na użycie, np.setInterpolator(new AccelerateDecelerateInterpolator())


1

Stworzyłem wersję, w której nie musisz określać wysokości układu, dlatego jest o wiele łatwiejszy i czystszy w użyciu. Rozwiązaniem jest uzyskanie wysokości w pierwszej klatce animacji (jest ona dostępna w tym momencie, przynajmniej podczas moich testów). W ten sposób możesz zapewnić widok z dowolną wysokością i dolnym marginesem.

Jest też jeden mały hack w konstruktorze - dolny margines jest ustawiony na -10000, więc widok pozostaje ukryty przed transformacją (zapobiega migotaniu).

public class ExpandAnimation extends Animation {


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

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

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

1

Użyj 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();

W moim przypadku nic nie robi. Możesz także złagodzić kod, zwijając do niego 2 wiersze mainView.getLayoutParams().height = height.
CoolMind,

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

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

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

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

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

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

Klasę można wywołać w następujący sposób

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

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

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

1

Na podstawie rozwiązań @Tom Esterez i @Seth Nelson (pierwsza 2) uprościłem je. Oprócz oryginalnych rozwiązań nie zależy to od opcji programisty (ustawienia animacji).

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

Po 3 latach ponownie przetestowałem kilka rozwiązań, ale tylko moje działało poprawnie.
CoolMind
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.