Android: Animation erweitern / reduzieren

449

Angenommen, ich habe ein vertikales lineares Layout mit:

[v1]
[v2]

Standardmäßig hat v1 sichtbar = GONE. Ich möchte v1 mit einer erweiterten Animation zeigen und gleichzeitig v2 nach unten drücken.

Ich habe so etwas versucht:

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

Aber mit dieser Lösung habe ich ein Blinken, wenn die Animation beginnt. Ich denke, es liegt daran, dass v1 in voller Größe angezeigt wird, bevor die Animation angewendet wird.

Mit Javascript ist dies eine Zeile von jQuery! Gibt es eine einfache Möglichkeit, dies mit Android zu tun?

Tom Esterez
quelle

Antworten:

734

Ich sehe, dass diese Frage populär wurde, also poste ich meine eigentliche Lösung. Der Hauptvorteil besteht darin, dass Sie die erweiterte Höhe nicht kennen müssen, um die Animation anzuwenden. Sobald die Ansicht erweitert ist, wird die Höhe angepasst, wenn sich der Inhalt ändert. Es funktioniert großartig für mich.

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

Wie von @Jefferson in den Kommentaren erwähnt, können Sie eine flüssigere Animation erhalten, indem Sie die Dauer (und damit die Geschwindigkeit) der Animation ändern. Derzeit wurde eine Geschwindigkeit von 1 dp / ms eingestellt

Tom Esterez
quelle
13
v.measure (MeasureSpec.makeMeasureSpec (LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec (LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); In einigen Fällen (my - ListView) führt diese Nichtübereinstimmung zu einem falschen Zielwert
Johnny Doe
12
@ Tom Esterez Das funktioniert, aber nicht sehr reibungslos. Gibt es zusätzliche Arbeiten, um es reibungslos zu machen?
acntwww
9
@acntwww Sie können eine reibungslose Animation erhalten, die die Dauer mit einem Faktor wie 4 multipliziert.a.setDuration(((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density)) * 4)
Jefferson Henrique C. Soares
10
@Alioo, importiere android.view.animation.Transformation;
Jomia
5
Funktioniert super! Ich hatte Probleme mit der gemessenen Höhe, da ich ein festes dp-Element erweitern wollte, also habe ich das Maß auf geändert v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));und v.getLayoutParams().height = interpolatedTime == 1 ? targetHeight : (int)(targetHeight * interpolatedTime);das hat bei mir funktioniert!
Vkislicins
140

Ich habe versucht, eine meiner Meinung nach sehr ähnliche Animation zu erstellen, und eine elegante Lösung gefunden. Dieser Code setzt voraus, dass Sie immer von 0-> h oder h-> 0 gehen (h ist die maximale Höhe). Die drei Konstruktorparameter sind view = die zu animierende Ansicht (in meinem Fall eine Webansicht), targetHeight = die maximale Höhe der Ansicht und down = ein Boolescher Wert, der die Richtung angibt (true = erweitern, false = reduzieren).

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
quelle
5
Der Code enthält einen Tippfehler: Der Methodenname "initalize" sollte "initialize" sein, sonst wird er nicht aufgerufen. ;) Ich würde empfehlen, in Zukunft @Override zu verwenden, damit diese Art von Tippfehler vom Compiler abgefangen wird.
Lorne Laliberte
4
Ich mache Folgendes: "DropDownAnim anim = new DropDownAnim (grid_titulos_atual, GRID_HEIGHT, true); anim.setDuration (500); anim.start ();" aber es funktioniert nicht. Ich habe einige Haltepunkte auf applyTransformation gesetzt, aber sie werden nie erreicht
Paulo Cesar
5
Ops, hat es zum Laufen gebracht, es ist view.startAnimation (a) ... Leistung ist nicht sehr gut, aber es funktioniert :)
Paulo Cesar
3
@IamStalker In dieser Situation sollten Sie wahrscheinlich mit zwei Variablen initialisieren, StartingHeight und EndingHeight. Wechseln Sie dann zu: if (down) {newHeight = (int) (((endingHeight-StartingHeight) * interpolatedTime) + StartingHeight); } else {newHeight = (int) (((endingHeight-StartingHeight) * (1 - interpolatedTime)) + StartingHeight); }
Seth Nelson
3
@Seth Ich denke, newHeight kann einfach (int) (((targetHeight -startingHeight) * interpolatedTime) + StartingHeight) sein, unabhängig von der Richtung, solange startHeight in initialize () festgelegt ist.
Giorgos Kylafas
138

Ich bin heute über das gleiche Problem gestolpert und ich denke, die wirkliche Lösung für diese Frage ist diese

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

Sie müssen diese Eigenschaft für alle obersten Layouts festlegen, die an der Verschiebung beteiligt sind. Wenn Sie jetzt die Sichtbarkeit eines Layouts auf GONE setzen, nimmt das andere den Platz ein, während das verschwindende ihn freigibt. Es wird eine Standardanimation geben, die eine Art "Ausblenden" darstellt, aber ich denke, Sie können dies ändern - aber die letzte, die ich noch nicht getestet habe.

Mr.Fu
quelle
2
+1, Jetzt suche ich nach Geschwindigkeit: Dauer von animateLayoutChanges
Tushar Pandey
9
Animieren von Layoutänderungen: developer.android.com/training/animation/layout.html
ccpizza
Es funktioniert nicht nach dem Drücken der Zurück-Taste. Irgendwelche Vorschläge?
Hassan Tareq
4
Dies funktioniert perfekt zum Erweitern der Animation, aber zum Reduzieren erfolgt die Animation, nachdem das übergeordnete Layout verkleinert wurde.
Shine_Joseph
3
@shine_joseph Ja, ich benutze dies in einem Recyclerview und wenn es zusammenbricht, sieht es wirklich komisch aus: /
AmirG
65

Ich habe die Lösung von @LenaYan genommen, die für mich nicht richtig funktioniert hat ( weil die Ansicht vor dem Reduzieren und / oder Erweitern in eine Ansicht mit einer Höhe von 0 umgewandelt wurde ) und einige Änderungen vorgenommen.

Jetzt funktioniert es hervorragend , indem Sie die vorherige Höhe der Ansicht übernehmen und mit dieser Größe erweitern. Das Zusammenfallen ist dasselbe.

Sie können den folgenden Code einfach kopieren und einfügen:

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

Verwendungszweck:

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

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

Leicht genug!

Danke LenaYan für den ersten Code!

Geraldo Neto
quelle
Obwohl es funktioniert, hängt es von den Entwicklereinstellungen (Animationsdauer) ab. Wenn es deaktiviert ist, wird keine Animation angezeigt.
CoolMind
Ja, aber es kann ein Problem sein oder auch nicht. Kommt auf deine Bewerbung an. Sie können beispielsweise die Animationsdauer mit einfachen Änderungen leicht proportional zur erweiterten / reduzierten Größe machen. Eine einstellbare Animationsdauer gibt Ihnen ein bisschen mehr Freiheit.
Geraldo Neto
Erweitern Sie Animation funktioniert nicht. es sieht aus wie eine Kollapsanimation.
Ahamadullah Saikat
39

Eine Alternative besteht darin, eine Skalierungsanimation mit den folgenden Skalierungsfaktoren zum Erweitern zu verwenden:

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

und zum Zusammenbruch:

ScaleAnimation anim = new ScaleAnimation(1, 1, 1, 0);
ChristophK
quelle
wie man die Animation startet .. View.startAnimation (anim); scheint nicht zu funktionieren
Mahendran
Genau so starte ich die Animation. Funktionieren andere Animationen für Sie?
ChristophK
1
Ging mit diesem Ansatz, funktioniert wie ein Zauber und keine Notwendigkeit, das zu implementieren, was bereits implementiert wurde.
Erbsman
15
Dadurch werden während der Animation keine Ansichten darunter nach unten gedrückt, und es wird angezeigt, als würde die animierte Ansicht von 0 -> h gestreckt.
5
Übrigens eignen sich Ansichtsanimationen hervorragend zum Skalieren: oView.animate (). ScaleY (0) zum vertikalen Reduzieren; oView.animate (). scaleY (1) zum Öffnen (beachten Sie, dass es nur ab SDK 12 verfügbar ist).
Kirk B.
27

@ Tom Esterez ' Antwort , aber aktualisiert, um view.measure () ordnungsgemäß für Android zu verwenden. GetMeasuredHeight gibt falsche Werte zurück!

    // 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.
quelle
1
Was ist addHeight und DURATION_MULTIPLIER?
MidasLefko
Vergessen Sie diese, addHeight ist für den Fall, dass Sie zusätzliche Höhe in Ihrer Erweiterung benötigen (wahrscheinlich nicht) und DURATION_MODIFIER ist nur ein Geschwindigkeitsmodifikator, falls Sie die Animationen beschleunigen / verlangsamen möchten.
Erik B
1
Funktioniert super! Bei Verwendung von TextView mit nur einem Wort in der letzten Zeile tritt eine kleine Verzögerung auf. Und können Sie erklären, was der PathInterpolator macht?
Yennsarah
Mit dem easyInOutQuart wird die Animation zuerst langsam, dann schnell und am Ende langsam, um ein sehr natürliches Gefühl zu erzeugen. Sie sprechen hier ausführlich darüber easings.net
Erik B
1
Ich habe Ihre Methode ausprobiert, aber wenn die Animation endet, ist meine Ansicht nicht mehr sichtbar.
Aman Verma
26

Ok, ich habe gerade eine SEHR hässliche Lösung gefunden:

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

Fühlen Sie sich frei, eine bessere Lösung vorzuschlagen!

Tom Esterez
quelle
3
+1, auch wenn dies als hässlich bezeichnet wird, funktioniert es für eine Ansicht, deren Größe wir noch nicht kennen (z. B. für den Fall, dass wir dem übergeordneten Element eine neu erstellte Ansicht (deren Größe FILL_PARENT ist) hinzufügen und animieren möchten dieser Prozess, einschließlich der Animation des Wachstums der übergeordneten Größe).
Vit Khudenko
Übrigens, es sieht so aus, als View.onMeause(widthMeasureSpec, heightMeasureSpec)ob der Aufruf einen kleinen Fehler enthält, daher sollten die Angaben zu Breite und Höhe ausgetauscht werden.
Vit Khudenko
22
public static void expand(final View v, int duration, int targetHeight) {
        v.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, targetHeight);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                v.getLayoutParams().height = (int) animation.getAnimatedValue();
                v.requestLayout();
            }
        });
        valueAnimator.setInterpolator(new DecelerateInterpolator());
        valueAnimator.setDuration(duration);
        valueAnimator.start();
    }
public static void collapse(final View v, int duration, int targetHeight) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, targetHeight);
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (int) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(duration);
    valueAnimator.start();
}
LenaYan
quelle
1
Ich habe dieses Problem ... der Inhalt in der zusammenklappbaren Ansicht verschwindet bei der Erweiterung. Ich habe die Recycler-Ansicht, die beim Erweitern dieser Ansicht verschwindet. @LenaYan
Akshay Mahajan
21

Wenn Sie nicht vollständig expandieren oder kollabieren möchten, finden Sie hier eine einfache 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;
    }
}

Verwendungszweck:

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

Ich habe die aktuell akzeptierte Antwort von Tom Esterez angepasst , die funktionierte, aber eine abgehackte und nicht sehr flüssige Animation hatte. Meine Lösung ersetzt im Grunde die Animationdurch eine ValueAnimator, die mit einer InterpolatorIhrer Wahl ausgestattet werden kann, um verschiedene Effekte wie Überschwingen, Abprallen, Beschleunigen usw. zu erzielen.

Diese Lösung eignet sich hervorragend für Ansichten mit einer dynamischen Höhe (z. B. mithilfe von WRAP_CONTENT), da zuerst die tatsächlich erforderliche Höhe gemessen und dann auf diese Höhe animiert wird.

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

Sie rufen dann einfach an expand( myView );oder collapse( myView );.

Magnus W.
quelle
Vielen Dank. Sie können auch eine Situation hinzufügen, in der die minimale Höhe nicht 0 ist.
CoolMind
Ich arbeite für mich für linearlayout
Roger
Habe gerade die in verwendeten Parameter korrigiert v.measure()und jetzt funktioniert es perfekt. Vielen Dank!
Shahood ul Hassan
8

Unter Verwendung der Kotlin-Erweiterungsfunktionen ist dies eine getestete und kürzeste Antwort

Rufen Sie einfach animateVisibility (Erweitern / Reduzieren) in einer beliebigen Ansicht auf.

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
quelle
wollte die gleiche Antwort posten :) Schade, dass dies hier so tief vergraben ist.
Muetzenflo
@muetzenflo Wenn immer mehr Leute die Antwort befürworten, wird sie auftauchen. :)
Rajkiran
Ich mochte diese Lösung, bis mir klar wurde, dass es eine Textansicht mit mehreren Zeilen mit einer Höhe von wrap_content gibt. Wenn sie erweitert wird, zeigt die Textansicht nur eine Zeile. Ich versuche jetzt zu beheben
olearyj234
7

Neben der hervorragenden Antwort von Tom Esterez und dem hervorragenden Update von Erik B dachte ich, ich würde meine eigene Einstellung veröffentlichen und die Expansions- und Kontraktionsmethoden zu einer einzigen komprimieren . Auf diese Weise könnten Sie beispielsweise eine Aktion wie diese ausführen ...

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

... die die folgende Methode aufruft und sie herausfinden lässt, was nach jedem onClick () zu tun ist ...

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
quelle
Ich habe diesen Code ausprobiert, aber damit er in mehreren Ansichten funktioniert, müssen Sie einen Bildlauf durchführen. Irgendeine Idee, wie ich das beheben kann? stackoverflow.com/q/43916369/1009507
sammyukavi
@Ukavi Ich verwende dies mit mehreren Ansichten und es funktioniert gut in einer ScrollView.
mjp66
Was ist mit einem Recyclerview?
Sammyukavi
@Ukavi musste es noch nicht in einem Recyclerview verwenden, aber ich kann nicht verstehen, warum es nicht funktionieren würde. Du musst selbst ein wenig damit experimentieren;)
mjp66
6

Ich möchte der sehr hilfreichen Antwort oben etwas hinzufügen . Wenn Sie die Höhe nicht kennen, mit der Sie enden werden, da Ihre Ansichten .getHeight () 0 zurückgeben, können Sie Folgendes tun, um die Höhe zu ermitteln:

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

Wobei DUMMY_HIGH_DIMENSIONS die Breite / Höhe (in Pixel) ist, auf die Ihre Ansicht beschränkt ist ... eine große Anzahl ist sinnvoll, wenn die Ansicht mit einer ScrollView gekapselt ist.

Gardarh
quelle
6

Dies ist ein Ausschnitt, mit dem ich die Breite einer Ansicht (LinearLayout) mit Animation geändert habe.

Der Code soll entsprechend der Zielgröße erweitert oder verkleinert werden. Wenn Sie eine fill_parent-Breite wünschen, müssen Sie die übergeordnete .getMeasuredWidth als Zielbreite übergeben, während Sie das Flag auf true setzen.

Hoffe es hilft einigen von euch.

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
quelle
Gibt es einen Trick, um dies zum Laufen zu bringen? Die Klasse erhält die richtigen Original- und Zielbreiten, aber meine Ansichten werden nicht in der Größe geändert. Ich benutze resizeAnim.start(). Habe es auch mit und ohne versuchtsetFillAfter(true)
Ben Kane
Ich habs. Musste .startAnimation(resizeAnim)auf die Aussicht anrufen .
Ben Kane
6

Verwenden Sie für eine reibungslose Animation den Handler mit der Ausführungsmethode ..... und genießen Sie die Animation zum Erweitern / Reduzieren

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

Und rufen Sie mit diesem Code an:

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

}

Andere Lösung ist:

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

kombinierte Lösungen von @Tom Esterez und @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);
Fluss
quelle
4

Ja, ich habe den obigen Kommentaren zugestimmt. In der Tat scheint es das Richtige (oder zumindest das Einfachste?) Zu sein, eine anfängliche Layouthöhe von "0px" (in XML) anzugeben - und dann können Sie ein anderes Argument für "toHeight" ( dh die "endgültige Höhe") zum Konstruktor Ihrer benutzerdefinierten Animationsunterklasse, z. B. im obigen Beispiel, würde es ungefähr so ​​aussehen:

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

Wie auch immer, hoffe das hilft! :) :)

Daniel Kopyc
quelle
4

Hier ist meine Lösung. Ich denke es ist einfacher. Es erweitert nur die Ansicht, kann aber einfach erweitert werden.

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

Ich denke, die einfachste Lösung besteht darin android:animateLayoutChanges="true", Ihre LinearLayoutAnsicht festzulegen und dann einfach anzuzeigen / auszublenden, indem Sie die Sichtbarkeit festlegen. Funktioniert wie ein Zauber, aber Sie haben keine Kontrolle über die Animationsdauer

Jacek Kwiecień
quelle
3

Du bist auf dem richtigen Weg. Stellen Sie sicher, dass v1 unmittelbar vor Beginn der Animation eine Layouthöhe von Null hat. Sie möchten Ihr Setup so initialisieren, dass es wie das erste Bild der Animation aussieht, bevor Sie die Animation starten.

Micah Hainline
quelle
Ich stimme zu, aber wie erhalte ich initialHeight (erforderlich für meine Animation), wenn ich dies tue?
Tom Esterez
Haben Sie versucht, die anfängliche Höhe beim Initialisieren zu speichern, die dort sichtbare Ansicht festzulegen und dann v.getLayoutParams () festzulegen. Height = 0; direkt danach alles in initialisieren?
Micah Hainline
Ja, wenn ich das tue, wird die Initialisierungsmethode mit height = 0
Tom Esterez
3

Dies war meine Lösung, ich ImageViewwächst von 100%zu 200%und kehre zu seiner ursprünglichen Größe zurück, indem ich zwei Animationsdateien im res/anim/Ordner verwende

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>

Senden Sie eine ImageViewan meine MethodesetAnimationGrowShrink()

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

setAnimationGrowShrink() Methode:

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

Dies ist eine ordnungsgemäß funktionierende Lösung, ich habe sie getestet:

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

Zusammenbruch:

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

Wertanimator:

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

Ansicht v ist die zu animierende Ansicht, PARENT_VIEW ist die Containeransicht, die die Ansicht enthält.

Anubhav
quelle
2

Mit droidQuery ist das ganz einfach . Betrachten Sie für den Start dieses 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>

Wir können die Höhe 100dpmit dem folgenden Code auf den gewünschten Wert animieren - sagen wir :

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

Dann droidQueryzum Animieren verwenden. Der einfachste Weg ist damit:

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

Um die Animation ansprechender zu gestalten, sollten Sie eine Lockerung hinzufügen:

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

Sie können die Dauer auch AnimationOptionsmithilfe der duration()Methode ändern oder festlegen, was passiert, wenn die Animation endet. Versuchen Sie für ein komplexes Beispiel:

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

Beste Lösung für Ansichten zum Erweitern / Reduzieren:

    @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();
    }
Владислав Стариков
quelle
Obwohl es funktioniert, hängt es auch von den Entwicklereinstellungen (Animationsdauer) ab. Und polieren Sie Ihren Code, löschen Sie die Lambda-Funktion und formatieren Sie sie neu onCheckedChanged.
CoolMind
Warum reicht es aus, requestLayout nur auf v aufzurufen, nachdem die LayoutParams von v geändert wurden? Ich dachte, es wäre notwendig, requestLayout auf vs Eltern
aufzurufen
2

Sie können einen ViewPropertyAnimator mit einer leichten Drehung verwenden. Skalieren Sie die Ansicht zum Reduzieren auf eine Höhe von 1 Pixel und blenden Sie sie dann aus. Zeigen Sie es zum Erweitern an und erweitern Sie es dann auf seine Höhe.

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

Der Pivot teilt der Ansicht mit, von wo aus skaliert werden soll. Die Standardeinstellung befindet sich in der Mitte. Die Dauer ist optional (Standard = 1000). Sie können auch den zu verwendenden Interpolator wie einstellen.setInterpolator(new AccelerateDecelerateInterpolator())

Freude
quelle
1

Ich habe eine Version erstellt, in der Sie keine Layouthöhe angeben müssen, daher ist die Verwendung viel einfacher und sauberer. Die Lösung besteht darin, die Höhe im ersten Frame der Animation zu ermitteln (sie ist zu diesem Zeitpunkt verfügbar, zumindest während meiner Tests). Auf diese Weise können Sie eine Ansicht mit einer beliebigen Höhe und einem beliebigen unteren Rand bereitstellen.

Es gibt auch einen kleinen Hack im Konstruktor - der untere Rand ist auf -10000 eingestellt, damit die Ansicht vor der Transformation ausgeblendet bleibt (verhindert Flimmern).

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.
quelle
1

Verwenden Sie 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
quelle
In meinem Fall macht nichts. Sie können auch Ihren Code vereinfachen, indem Sie 2 Zeilen in reduzieren 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);
Kerosin
quelle
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;
    }
}

Die Klasse kann folgendermaßen aufgerufen werden

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

Basierend auf Lösungen von @Tom Esterez und @Seth Nelson (Top 2) habe ich sie vereinfacht. Neben den ursprünglichen Lösungen hängt es nicht von den Entwickleroptionen (Animationseinstellungen) ab.

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
quelle
Nun, nach 3 Jahren habe ich wieder mehrere Lösungen getestet, aber nur meine hat richtig funktioniert.
CoolMind