Android: espandere / comprimere l'animazione


449

Diciamo che ho un layout lineare verticale con:

[v1]
[v2]

Di default v1 ha visibily = GONE. Vorrei mostrare v1 con un'animazione di espansione e spingere verso il basso v2 allo stesso tempo.

Ho provato qualcosa del genere:

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

Ma con questa soluzione, ho un battito di ciglia quando inizia l'animazione. Penso che sia causato dalla visualizzazione a schermo intero di v1 prima dell'applicazione dell'animazione.

Con javascript, questa è una riga di jQuery! Un modo semplice per farlo con Android?

Risposte:


734

Vedo che questa domanda è diventata popolare, quindi inserisco la mia soluzione effettiva. Il vantaggio principale è che non è necessario conoscere l'altezza espansa per applicare l'animazione e, una volta espansa la vista, adatta l'altezza se il contenuto cambia. Mi va benissimo.

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

Come menzionato da @Jefferson nei commenti, è possibile ottenere un'animazione più fluida modificando la durata (e quindi la velocità) dell'animazione. Attualmente, è stato impostato a una velocità di 1dp / ms


13
v.measure (MeasureSpec.makeMeasureSpec (LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec (LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY)); In alcuni casi (my - ListView) questa mancata corrispondenza porta a target errato Valore di altezza
Johnny Doe,

12
@Tom Esterez Questo funziona, ma non molto agevolmente. C'è qualche lavoro aggiuntivo per farlo senza intoppi?
acntwww

9
@acntwww È possibile ottenere un'animazione fluida moltiplicando la durata per un fattore, ad esempio 4.a.setDuration(((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density)) * 4)
Jefferson Henrique C. Soares

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

5
Funziona alla grande! Ho avuto problemi con l'altezza misurata perché volevo espandere un elemento dp fisso, quindi ho cambiato la misura v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));e ha v.getLayoutParams().height = interpolatedTime == 1 ? targetHeight : (int)(targetHeight * interpolatedTime);funzionato per me!
vkislicins,

140

Stavo cercando di fare quello che credo fosse un'animazione molto simile e ho trovato una soluzione elegante. Questo codice presuppone che tu vada sempre da 0-> h o h-> 0 (h è l'altezza massima). I tre parametri del costruttore sono view = la vista da animare (nel mio caso, una webview), targetHeight = l'altezza massima della vista e down = un valore booleano che specifica la direzione (vero = espandibile, falso = collasso).

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
C'è un refuso nel codice: il nome del metodo "initalize" dovrebbe essere "inizializza" o non verrà chiamato. ;) Consiglierei di usare @Override in futuro in modo che questo tipo di refuso venga catturato dal compilatore.
Lorne Laliberte,

4
Sto facendo quanto segue: "DropDownAnim anim = new DropDownAnim (grid_titulos_atual, GRID_HEIGHT, true); anim.setDuration (500); anim.start ();" ma non funziona. Ho inserito alcuni punti di interruzione su applyTransformation ma non sono mai stati raggiunti
Paulo Cesar,

5
Ops, ha funzionato, è view.startAnimation (a) ... Le prestazioni non sono molto buone, ma funzionano :)
Paulo Cesar,

3
@IamStalker In quella situazione, probabilmente dovresti inizializzare con due variabili, inizioHeight e endingHeight. Quindi cambia in: if (down) {newHeight = (int) ((((endingHeight-startingHeight) * interpolatedTime) + startingHeight); } else {newHeight = (int) (((endingHeight-startingHeight) * (1 - interpolatedTime)) + startingHeight); }
Seth Nelson,

3
@Seth Penso che newHeight possa essere semplicemente (int) (((targetHeight -startingHeight) * interpolatedTime) + startingHeight), indipendentemente dalla direzione, purché startHeight sia impostato in initialize ().
Giorgos Kylafas,

138

Mi sono imbattuto nello stesso problema oggi e immagino che la vera soluzione a questa domanda sia questa

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

Dovrai impostare questa proprietà per tutti i layout più in alto, che sono coinvolti nel turno. Se ora imposti la visibilità di un layout su GONE, l'altro occuperà lo spazio mentre quello che scompare lo sta rilasciando. Ci sarà un'animazione predefinita che è una sorta di "dissolvenza", ma penso che tu possa cambiarla, ma l'ultima che non ho ancora testato, per ora.


2
+1, Ora sto cercando Speed: durata di animateLayoutChanges
Tushar Pandey

9
Modifiche al layout di animazione: developer.android.com/training/animation/layout.html
ccpizza,

Non funziona dopo aver premuto il pulsante Indietro. Eventuali suggerimenti?
Hassan Tareq,

4
Funziona perfettamente per espandere l'animazione, ma per il collasso l'animazione ha luogo dopo la riduzione del layout principale.
shine_joseph,

3
@shine_joseph sì, lo sto usando all'interno di una recyclerview e quando il collasso sembra davvero strano: /
AmirG

65

Ho preso la soluzione di @LenaYan che non ha funzionato correttamente per me ( perché stava trasformando la vista in una vista ad altezza 0 prima di crollare e / o espandere ) e ho apportato alcune modifiche.

Ora funziona benissimo , prendendo l' altezza precedente della vista e inizia ad espandersi con queste dimensioni. Il collasso è lo stesso.

Puoi semplicemente copiare e incollare il codice qui sotto:

public static void expand(final View v, int duration, int targetHeight) {

    int prevHeight  = v.getHeight();

    v.setVisibility(View.VISIBLE);
    ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (int) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(duration);
    valueAnimator.start();
}

public static void collapse(final View v, int duration, int targetHeight) {
    int prevHeight  = v.getHeight();
    ValueAnimator valueAnimator = ValueAnimator.ofInt(prevHeight, targetHeight);
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            v.getLayoutParams().height = (int) animation.getAnimatedValue();
            v.requestLayout();
        }
    });
    valueAnimator.setInterpolator(new DecelerateInterpolator());
    valueAnimator.setDuration(duration);
    valueAnimator.start();
}

Uso:

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

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

Abbastanza facile!

Grazie LenaYan per il codice iniziale!


Sebbene funzioni, dipende dalle impostazioni dello sviluppatore (durata dell'animazione). Se è disabilitato, non verrà mostrata alcuna animazione.
CoolMind,

Sì, ma potrebbe essere o meno un problema. Dipende dalla tua applicazione. Ad esempio, è possibile rendere facilmente la durata dell'animazione proporzionale alla dimensione espansa / compressa con semplici modifiche. Avere una durata di animazione impostabile ti dà un po 'più di libertà.
Geraldo Neto,

Espandi l'animazione non funziona. sembra animare il collasso.
Ahamadullah Saikat,

39

Un'alternativa è utilizzare un'animazione in scala con i seguenti fattori di scala per l'espansione:

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

e per il collasso:

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

come avviare l'animazione .. View.startAnimation (anim); sembra non funzionare
Mahendran,

questo è esattamente il modo in cui inizio l'animazione. Altre animazioni funzionano per te?
ChristophK,

1
Sono andato con questo approccio, funziona come un incantesimo e non è necessario implementare ciò che è già stato implementato.
Erbsman,

15
Questo non spinge verso il basso le viste sottostanti durante l'animazione e appare come se allungasse la vista animata da 0 -> h.

5
A proposito, le animazioni di visualizzazione funzionano perfettamente per il ridimensionamento: oView.animate (). ScaleY (0) per comprimere verticalmente; oView.animate (). scaleY (1) da aprire (nota che è disponibile solo da SDK 12 e versioni successive).
Kirk B.

27

La risposta di Tom Esterez , ma aggiornata per usare view.measure () correttamente per Android getMeasuredHeight restituisce valori errati!

    // 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
che cos'è addHeight e DURATION_MULTIPLIER?
MidasLefko,

Dimenticato quelli, addHeight è nel caso in cui tu abbia bisogno di un'altezza extra nella tua espansione (probabilmente no) e DURATION_MODIFIER è solo un modificatore di velocità nel caso tu voglia accelerare / rallentare le animazioni.
Erik B,

1
Funziona alla grande! Si verifica un piccolo ritardo durante l'utilizzo di TextView con una sola parola sull'ultima riga. E potresti spiegare cosa fa PathInterpolator ...?
yennsarah,

EasyInOutQuart rende inizialmente lenta l'animazione, quindi veloce, quindi lenta alla fine per una sensazione molto naturale. Ne parlano a fondo qui easings.net
Erik B

1
ho provato il tuo metodo ma ogni volta che l'animazione termina la mia vista non è più visibile.
Aman Verma,

26

Ok, ho appena trovato una soluzione MOLTO brutta:

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

Sentiti libero di proporre una soluzione migliore!


3
+1, anche se questo è chiamato come brutto, funziona per una vista in cui non conosciamo ancora le sue dimensioni (ad esempio nel caso in cui stiamo aggiungendo una vista appena creata (la cui dimensione è FILL_PARENT) al genitore e vorrebbe animare questo processo, inclusa l'animazione della crescita delle dimensioni del genitore).
Vit Khudenko,

A proposito, sembra che ci sia un piccolo errore View.onMeause(widthMeasureSpec, heightMeasureSpec)nell'invocazione, quindi le specifiche di larghezza e altezza dovrebbero essere scambiate.
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
Ho questo problema ... il contenuto all'interno della vista pieghevole sta scomparendo con l'espansione. Ho la vista Riciclatore che scompare con l'espansione di questa vista. @LenaYan
Akshay Mahajan,

21

Se non vuoi espandere o comprimere fino in fondo - ecco una semplice HeightAnimation -

import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;

public class HeightAnimation extends Animation {
    protected final int originalHeight;
    protected final View view;
    protected float perValue;

    public HeightAnimation(View view, int fromHeight, int toHeight) {
        this.view = view;
        this.originalHeight = fromHeight;
        this.perValue = (toHeight - fromHeight);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        view.getLayoutParams().height = (int) (originalHeight + perValue * interpolatedTime);
        view.requestLayout();
    }

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

Uso:

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

13

Ho adattato la risposta attualmente accettata da Tom Esterez , che ha funzionato ma ha avuto un'animazione agitata e non molto fluida. La mia soluzione in sostanza sostituisce la Animationcon a ValueAnimator, che può essere equipaggiata con una a Interpolatortua scelta per ottenere vari effetti come il superamento, il rimbalzo, l'accelerazione, ecc.

Questa soluzione funziona alla grande con viste che hanno un'altezza dinamica (cioè usando WRAP_CONTENT), poiché misura prima l'altezza effettiva richiesta e quindi si anima a tale altezza.

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

Quindi semplicemente chiamare expand( myView );o collapse( myView );.


Grazie. Puoi anche aggiungere una situazione in cui l'altezza minima non è 0.
CoolMind

lavoro per me per linearlayout
Roger

Ho appena corretto i parametri utilizzati v.measure()e ora funziona perfettamente. Grazie!
Shahood ul Hassan

9

Facendo uso delle funzioni di estensione di Kotlin questa è la risposta testata e più breve

Chiama animateVisibility (espandi / comprimi) su qualsiasi vista.

fun View.animateVisibility(setVisible: Boolean) {
    if (setVisible) expand(this) else collapse(this)
}

private fun expand(view: View) {
    view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
    val initialHeight = 0
    val targetHeight = view.measuredHeight

    // Older versions of Android (pre API 21) cancel animations for views with a height of 0.
    //v.getLayoutParams().height = 1;
    view.layoutParams.height = 0
    view.visibility = View.VISIBLE

    animateView(view, initialHeight, targetHeight)
}

private fun collapse(view: View) {
    val initialHeight = view.measuredHeight
    val targetHeight = 0

    animateView(view, initialHeight, targetHeight)
}

private fun animateView(v: View, initialHeight: Int, targetHeight: Int) {
    val valueAnimator = ValueAnimator.ofInt(initialHeight, targetHeight)
    valueAnimator.addUpdateListener { animation ->
        v.layoutParams.height = animation.animatedValue as Int
        v.requestLayout()
    }
    valueAnimator.addListener(object : Animator.AnimatorListener {
        override fun onAnimationEnd(animation: Animator) {
            v.layoutParams.height = targetHeight
        }

        override fun onAnimationStart(animation: Animator) {}
        override fun onAnimationCancel(animation: Animator) {}
        override fun onAnimationRepeat(animation: Animator) {}
    })
    valueAnimator.duration = 300
    valueAnimator.interpolator = DecelerateInterpolator()
    valueAnimator.start()
}

volevo pubblicare la stessa risposta :) Peccato che qui sia sepolto così in profondità.
muetzenflo,

@muetzenflo Se sempre più persone votano la risposta, verrà fuori. :)
Rajkiran,

Mi è piaciuta questa soluzione fino a quando ho capito se c'è una visualizzazione di testo con più righe con un'altezza di wrap_content, quando espansa, la visualizzazione di testo mostrerà solo una riga. Sto cercando di risolvere ora
olearyj234 il

Ho provato questo, ma l'animazione non sembra essere fluida. Per espandere, viene visualizzata brevemente l'intera visualizzazione di testo e viene riprodotta l'animazione. Per il collasso, la visualizzazione testuale si espande immediatamente subito dopo il collasso, per qualche motivo. Hai idea di cosa sto facendo di sbagliato?
Anchith Acharya

7

Aggiungendo all'eccellente risposta di Tom Esterez e all'eccellente aggiornamento di Erik B , ho pensato di pubblicare il mio take, comprimendo i metodi di espansione e contratto in uno. In questo modo, ad esempio, potresti avere un'azione come questa ...

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

... che chiama il metodo seguente e lascia che capisca cosa fare dopo ogni 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);

}

Ho provato questo codice ma per rimorchiare il lavoro su più viste, è necessario scorrere. Qualche idea su come posso risolvere questo problema? stackoverflow.com/q/43916369/1009507
sammyukavi

@Ukavi Lo sto usando con più viste e funziona benissimo all'interno di ScrollView.
mjp66,

Che dire di un recyclerview?
sammyukavi,

@Ukavi non ha ancora avuto bisogno di usarlo in una ricicletta ma non riesco a capire perché non funzioni. Dovrai sperimentarlo un po 'da solo;)
mjp66,

6

Vorrei aggiungere qualcosa alla risposta molto utile sopra . Se non conosci l'altezza con cui finirai poiché le tue visualizzazioni .getHeight () restituisce 0, puoi ottenere quanto segue per ottenere l'altezza:

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

Dove DUMMY_HIGH_DIMENSIONS è la larghezza / altezza (in pixel) alla quale la tua vista è limitata ... avere un numero enorme è ragionevole quando la vista è incapsulata con una ScrollView.


6

Questo è uno snippet che ho usato per ridimensionare la larghezza di una vista (LinearLayout) con l'animazione.

Il codice dovrebbe espandersi o ridursi in base alle dimensioni del target. Se desideri una larghezza fill_parent, dovrai passare il genitore .getMeasuredWidth come larghezza target mentre imposti il ​​flag su true.

Spero che possa aiutare qualcuno di voi.

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

}


C'è qualche trucco per farlo funzionare? La classe ottiene le larghezze originali e di destinazione corrette, ma le mie visualizzazioni non verranno ridimensionate. Sto usando resizeAnim.start(). Ho anche provato con e senzasetFillAfter(true)
Ben Kane il

Fatto. Ho dovuto chiamare .startAnimation(resizeAnim)la vista.
Ben Kane,

6

Per animazioni fluide, utilizza Handler con il metodo run ..... E goditi l'animazione Espandi / Comprimi

    class AnimUtils{

                 public void expand(final View v) {
                  int ANIMATION_DURATION=500;//in milisecond
        v.measure(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        final int targtetHeight = v.getMeasuredHeight();

        v.getLayoutParams().height = 0;
        v.setVisibility(View.VISIBLE);
        Animation a = new Animation()
        {
            @Override
            protected void applyTransformation(float interpolatedTime, Transformation t) {
                v.getLayoutParams().height = interpolatedTime == 1
                        ? LayoutParams.WRAP_CONTENT
                        : (int)(targtetHeight * interpolatedTime);
                v.requestLayout();
            }

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

        // 1dp/ms
        a.setDuration(ANIMATION_DURATION);

      // a.setDuration((int)(targtetHeight / v.getContext().getResources().getDisplayMetrics().density));
        v.startAnimation(a);
    }



    public void collapse(final View v) {
        final int initialHeight = v.getMeasuredHeight();

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

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

        // 1dp/ms
        a.setDuration(ANIMATION_DURATION);
       // a.setDuration((int)(initialHeight / v.getContext().getResources().getDisplayMetrics().density));
        v.startAnimation(a);
    }
}

E chiama usando questo codice:

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

}

Altra soluzione è:

               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

soluzioni combinate di @Tom Esterez e @Geraldo Neto

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

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

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

4

Sì, sono d'accordo con i commenti sopra. E in effetti, sembra che la cosa giusta (o almeno la più semplice?) Da fare sia specificare (in XML) un'altezza di layout iniziale di "0px" - e quindi puoi passare un altro argomento per "toHeight" ( vale a dire l '"altezza finale") per il costruttore della sottoclasse di animazione personalizzata, ad esempio nell'esempio sopra, sarebbe simile al seguente:

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

Spero comunque che ti aiuti! :)


4

Ecco la mia soluzione Penso che sia più semplice. Espande solo la vista ma può essere facilmente esteso.

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

Penso che la soluzione più semplice sia impostare la android:animateLayoutChanges="true"tua LinearLayoute quindi mostrare / nascondere la vista impostandone la visibilità. Funziona come un incantesimo, ma non hai alcun controllo sulla durata dell'animazione


3

Sei sulla strada giusta. Assicurati di avere v1 impostato per avere un'altezza di layout pari a zero subito prima dell'inizio dell'animazione. Si desidera inizializzare la configurazione in modo che assomigli al primo fotogramma dell'animazione prima di iniziare l'animazione.


Sono d'accordo ma come ottenere initialHeight (richiesto dalla mia animazione) se lo faccio?
Tom Esterez,

Hai provato effettivamente a salvare l'altezza iniziale in inizializza, impostando la vista lì visibile e quindi impostando v.getLayoutParams (). Height = 0; subito dopo, tutto in inizializzare?
Micah Hainline,

Sì, se lo faccio, il metodo di inizializzazione viene chiamato con height = 0
Tom Esterez,

3

Questa è stata la mia soluzione, il mio ImageViewcresce da 100%a 200%e il ritorno alla sua dimensione originale, utilizzando due file di animazione all'interno res/anim/della cartella

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>

Invia un ImageViewal mio metodosetAnimationGrowShrink()

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

setAnimationGrowShrink() metodo:

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

Questa è una soluzione funzionante, l'ho testata:

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

Crollo:

private void collapse(View v) {
    int finalHeight = v.getHeight();

    mAnimator = slideAnimator(finalHeight, 0);

    mAnimator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animator) {

        }

        @Override
        public void onAnimationEnd(Animator animator) {
            //Height=0, but it set visibility to GONE
            llDescp.setVisibility(View.GONE);
        }

        @Override
        public void onAnimationCancel(Animator animator) {

        }

        @Override
        public void onAnimationRepeat(Animator animator) {

        }
    });
    mAnimator.start();
}

Value Animator:

private ValueAnimator slideAnimator(int start, int end) {
    ValueAnimator mAnimator = ValueAnimator.ofInt(start, end);

    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            //Update Height
            int value = (Integer) valueAnimator.getAnimatedValue();
            ViewGroup.LayoutParams layoutParams = llDescp.getLayoutParams();
            layoutParams.height = value;
            v.setLayoutParams(layoutParams);
        }
    });
    return mAnimator;
}

Vista v è la vista da animare, PARENT_VIEW è la vista contenitore che contiene la vista.


2

Questo è davvero semplice con droidQuery . Per iniziare, considera questo 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>

Possiamo animare l'altezza al valore desiderato - diciamo 100dp- usando il seguente codice:

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

Quindi utilizzare droidQueryper animare. Il modo più semplice è con questo:

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

Per rendere l'animazione più accattivante, potresti aggiungere un allentamento:

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

Puoi anche modificare la durata AnimationOptionsutilizzando il duration()metodo o gestire ciò che accade al termine dell'animazione. Per un esempio complesso, prova:

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

La migliore soluzione per espandere / comprimere le viste:

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

Sebbene funzioni, dipende anche dalle impostazioni dello sviluppatore (durata dell'animazione). E lucidare il codice, eliminare la funzione lambda e riformattare onCheckedChanged.
CoolMind,

Perché è sufficiente chiamare requestLayout solo su v dopo aver modificato LayoutParams di v? Ho pensato che sarebbe stato necessario chiamare requestLayout sul genitore di v
vlazzle

2

È possibile utilizzare un ViewPropertyAnimator con una leggera rotazione. Per comprimere, ridimensionare la vista ad un'altezza di 1 pixel, quindi nasconderla. Per espanderlo, mostralo, quindi espanderlo alla sua altezza.

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

Il perno indica alla vista da dove ridimensionare, il valore predefinito è nel mezzo. La durata è facoltativa (impostazione predefinita = 1000). Puoi anche impostare l'interpolatore da usare, ad esempio.setInterpolator(new AccelerateDecelerateInterpolator())


1

Ho creato una versione in cui non è necessario specificare l'altezza del layout, quindi è molto più semplice e pulita da usare. La soluzione è quella di ottenere l'altezza nel primo fotogramma dell'animazione (è disponibile in quel momento, almeno durante i miei test). In questo modo è possibile fornire una vista con altezza e margine inferiore arbitrari.

C'è anche un piccolo trucco nel costruttore: il margine inferiore è impostato su -10000 in modo che la vista rimanga nascosta prima della trasformazione (previene lo sfarfallio).

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

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

Nel mio caso non fa nulla. Inoltre puoi facilitare il tuo codice, comprimendo 2 righe in 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;
    }
}

La classe può essere chiamata nel modo seguente

   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

Basandomi sulle soluzioni di @Tom Esterez e @Seth Nelson (top 2) le ho semplificate. Oltre alle soluzioni originali non dipende dalle opzioni dello sviluppatore (impostazioni di animazione).

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

Bene, dopo 3 anni ho testato di nuovo diverse soluzioni, ma solo la mia ha funzionato bene.
CoolMind
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.