Jak animować View.setVisibility (GONE)

84

Chcę zrobić, Animationgdy Viewzostanie ustawiona widoczność GONE. Zamiast po prostu zniknąć, Viewpowinno się „zawalić”. Próbowałem tego z a, ScaleAnimationale potem Viewjest zwijanie, ale układ zmieni rozmiar tylko po (lub przed) Animationzatrzymaniami (lub startami).

Jak mogę sprawić Animation, by podczas animacji dolne Views pozostawały bezpośrednio pod treścią zamiast pustej przestrzeni?

MrSnowflake
źródło
Użyłem tej samej techniki, którą przedstawił tutaj Andy, na moim ExpandAnimation: udinic.wordpress.com/2011/09/03/expanding-listview-items Nie użyłem animacji skali, właśnie utworzyłem nową animację klasa za to.
Udinic,
Było to bardzo przydatne, gdy próbowałem to zrobić. Dziękuję
pochwala
Doskonały Udinic .. naprawdę rozwiązał mój problem .. :) dzięki
Yousuf Qureshi
Świetnie, muszę dostosować się do mojego problemu, ale w końcu to działa. Dla mnie to rozwiązanie było lepsze niż inna odpowiedź.
Derzu

Odpowiedzi:

51

Wydaje się, że nie ma łatwego sposobu na zrobienie tego za pośrednictwem interfejsu API, ponieważ animacja zmienia tylko macierz renderowania widoku, a nie rzeczywisty rozmiar. Ale możemy ustawić ujemny margines, aby oszukać LinearLayout, myśląc, że widok się zmniejsza.

Dlatego zalecałbym utworzenie własnej klasy Animation w oparciu o ScaleAnimation i zastąpienie metody „applyTransformation”, aby ustawić nowe marginesy i zaktualizować układ. Lubię to...

public class Q2634073 extends Activity implements OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.q2634073);
        findViewById(R.id.item1).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        view.startAnimation(new MyScaler(1.0f, 1.0f, 1.0f, 0.0f, 500, view, true));
    }

    public class MyScaler extends ScaleAnimation {

        private View mView;

        private LayoutParams mLayoutParams;

        private int mMarginBottomFromY, mMarginBottomToY;

        private boolean mVanishAfter = false;

        public MyScaler(float fromX, float toX, float fromY, float toY, int duration, View view,
                boolean vanishAfter) {
            super(fromX, toX, fromY, toY);
            setDuration(duration);
            mView = view;
            mVanishAfter = vanishAfter;
            mLayoutParams = (LayoutParams) view.getLayoutParams();
            int height = mView.getHeight();
            mMarginBottomFromY = (int) (height * fromY) + mLayoutParams.bottomMargin - height;
            mMarginBottomToY = (int) (0 - ((height * toY) + mLayoutParams.bottomMargin)) - height;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            if (interpolatedTime < 1.0f) {
                int newMarginBottom = mMarginBottomFromY
                        + (int) ((mMarginBottomToY - mMarginBottomFromY) * interpolatedTime);
                mLayoutParams.setMargins(mLayoutParams.leftMargin, mLayoutParams.topMargin,
                    mLayoutParams.rightMargin, newMarginBottom);
                mView.getParent().requestLayout();
            } else if (mVanishAfter) {
                mView.setVisibility(View.GONE);
            }
        }

    }

}

Obowiązuje zwykłe zastrzeżenie: ponieważ zastępujemy metodę chronioną (applyTransformation), nie ma gwarancji, że zadziała to w przyszłych wersjach Androida.

Andy
źródło
3
Dlaczego do diabła o tym nie pomyślałem ?! Dziękuję Ci. Nie otrzymuję również komunikatu: „Obowiązuje zwykłe zastrzeżenie: ponieważ zastępujemy metodę chronioną (applyTransformation), nie ma gwarancji, że zadziała to w przyszłych wersjach Androida”. - Dlaczego chronione funkcje miałyby różnić się w zależności od wersji API? Nie są one ukryte i są zaimplementowane jako chronione, dzięki czemu można je przesłonić (w przeciwnym razie byłyby objęte zakresem pakietu).
MrSnowflake
Prawdopodobnie masz rację co do metody chronionej. Jestem zbyt ostrożny, jeśli chodzi o dostęp do nich w API.
Andy
1
To działało świetnie, z wyjątkiem tego, że aby „zwijanie” działało (od Y = 0,0f, toY = 1,0f), musiałem usunąć 0 - z marginBottomToYobliczeń.
dmon
2
Sugerowałbym użycie typu ogólnego MarginLayoutParamszamiast rzutowania go na określony LayoutParamtyp.
Paul Lammertsma
3
Załóżmy, że muszę zrobić animację przełączania, a następnie jak można to zrobić w odwrotnej kolejności. Proszę o poradę.
Umesh
99

Umieść widok w układzie, jeśli nie jest, i ustaw android:animateLayoutChanges="true"dla tego układu.

Vinay W
źródło
1
Minimalny wymagany interfejs API to 11 lub nowszy! Nie można użyć tej metody w przypadku niższej wersji.
Nagaraj Alagusudaram
2
to najbardziej niedoceniany atrybut układu ... dziękuję!
Andres Santiago,
7

Użyłem tej samej techniki, którą przedstawił Andy. Napisałem w tym celu własną klasę Animation, która animuje wartość marginesu, powodując zniknięcie / pojawienie się efektu elementu. To wygląda tak:

public class ExpandAnimation extends Animation {

// Initializations...

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    super.applyTransformation(interpolatedTime, t);

    if (interpolatedTime < 1.0f) {

        // Calculating the new bottom margin, and setting it
        mViewLayoutParams.bottomMargin = mMarginStart
                + (int) ((mMarginEnd - mMarginStart) * interpolatedTime);

        // Invalidating the layout, making us seeing the changes we made
        mAnimatedView.requestLayout();
    }
}
}

Mam pełny przykład, który działa na moim blogu http://udinic.wordpress.com/2011/09/03/expanding-listview-items/

Udinic
źródło
2

Użyłem tutaj tej samej techniki co Andy i dopracowałem ją tak, aby można było jej używać do rozwijania i zwijania bez zakłóceń, również przy użyciu techniki opisanej tutaj: https://stackoverflow.com/a/11426510/1317564

import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.ScaleAnimation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;

class LinearLayoutVerticalScaleAnimation extends ScaleAnimation {
    private final LinearLayout view;
    private final LinearLayout.LayoutParams layoutParams;

    private final float beginY;
    private final float endY;
    private final int originalBottomMargin;

    private int expandedHeight;
    private boolean marginsInitialized = false;
    private int marginBottomBegin;
    private int marginBottomEnd;

    private ViewTreeObserver.OnPreDrawListener preDrawListener;

    LinearLayoutVerticalScaleAnimation(float beginY, float endY,
            LinearLayout linearLayout) {
        super(1f, 1f, beginY, endY);

        this.view = linearLayout;
        this.layoutParams = (LinearLayout.LayoutParams) linearLayout.getLayoutParams();

        this.beginY = beginY;
        this.endY = endY;
        this.originalBottomMargin = layoutParams.bottomMargin;

        if (view.getHeight() != 0) {
            expandedHeight = view.getHeight();
            initializeMargins();
        }
    }

    private void initializeMargins() {
        final int beginHeight = (int) (expandedHeight * beginY);
        final int endHeight = (int) (expandedHeight * endY);

        marginBottomBegin = beginHeight + originalBottomMargin - expandedHeight;
        marginBottomEnd = endHeight + originalBottomMargin - expandedHeight;
        marginsInitialized = true;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);     

        if (!marginsInitialized && preDrawListener == null) {                       
            // To avoid glitches, don't draw until we've initialized everything.
            preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {                    
                    if (view.getHeight() != 0) {
                        expandedHeight = view.getHeight();
                        initializeMargins();
                        adjustViewBounds(0f);
                        view.getViewTreeObserver().removeOnPreDrawListener(this);                               
                    }

                    return false;
                }
            };

            view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);                   
        }

        if (interpolatedTime < 1.0f && view.getVisibility() != View.VISIBLE) {          
            view.setVisibility(View.VISIBLE);           
        }

        if (marginsInitialized) {           
            if (interpolatedTime < 1.0f) {
                adjustViewBounds(interpolatedTime);
            } else if (endY <= 0f && view.getVisibility() != View.GONE) {               
                view.setVisibility(View.GONE);
            }
        }
    }

    private void adjustViewBounds(float interpolatedTime) {
        layoutParams.bottomMargin = 
                marginBottomBegin + (int) ((marginBottomEnd - marginBottomBegin) * interpolatedTime);       

        view.getParent().requestLayout();
    }
}
Naucz się OpenGL ES
źródło
Czy można użyć tego, aby najpierw zwinąć istniejący LinearLayout, a następnie ponownie rozwinąć ten sam LinearLayout? Kiedy próbuję to zrobić, po prostu się zwija i nie rozszerza się ponownie (prawdopodobnie dlatego, że wysokość widoku wynosi teraz 0 lub coś w tym rodzaju).
AHaahr
Odkryłem, że działa bardziej niezawodnie, gdy układ liniowy zawiera więcej niż jeden widok. Jeśli zawiera tylko jeden widok, nie zawsze się rozwija.
Naucz się OpenGL ES