Esempio di lavoro completo dello scenario di animazione a tre frammenti di Gmail?


128

TL; DR: Sto cercando un esempio completo di funzionamento di quello che chiamerò scenario "l'animazione a tre frammenti di Gmail". In particolare, vogliamo iniziare con due frammenti, in questo modo:

due frammenti

In occasione di un evento dell'interfaccia utente (ad esempio, toccando qualcosa nel frammento B), vogliamo:

  • Frammento A per scorrere fuori dallo schermo a sinistra
  • Frammento B per scorrere verso il bordo sinistro dello schermo e ridursi per occupare il punto lasciato libero dal frammento A
  • Frammento C per scorrere dal lato destro dello schermo e occupare il posto lasciato libero dal frammento B

E, premendo un pulsante INDIETRO, vogliamo che tale serie di operazioni sia invertita.

Ora ho visto molte implementazioni parziali; Ne esaminerò quattro di seguito. Oltre ad essere incompleti, hanno tutti i loro problemi.


@Reto Meier ha contribuito con questa popolare risposta alla stessa domanda di base, indicando che avresti usato setCustomAnimations()con a FragmentTransaction. Per uno scenario a due frammenti (ad esempio, inizialmente vedi solo il frammento A e vuoi sostituirlo con un nuovo frammento B usando effetti animati), sono completamente d'accordo. Però:

  • Dato che puoi specificare solo un'animazione "in" e una "out", non riesco a vedere come gestiresti tutte le diverse animazioni richieste per lo scenario a tre frammenti
  • Nel <objectAnimator>suo codice di esempio utilizza posizioni cablate in pixel e ciò sembrerebbe poco pratico date le dimensioni dello schermo variabili, tuttavia setCustomAnimations()richiede risorse di animazione, precludendo la possibilità di definire queste cose in Java
  • Sono in perdita su come gli animatori di oggetti per la scala si collegano a cose come android:layout_weightin una LinearLayoutallocazione dello spazio su base percentuale
  • Sono ad una perdita su come frammento C viene gestita, in via preliminare ( GONE? android:layout_weightDi 0? Pre-animato per una scala da 0? Qualcos'altro?)

@Roman Nurik sottolinea che puoi animare qualsiasi proprietà , comprese quelle che definisci tu stesso. Ciò può aiutare a risolvere il problema delle posizioni cablate, a costo di inventare la propria sottoclasse del gestore di layout personalizzato. Questo aiuta alcuni, ma sono ancora sconcertato dal resto della soluzione di Reto.


L'autore di questa voce pastebin mostra alcuni pseudocodici allettanti, sostanzialmente dicendo che tutti e tre i frammenti risiederebbero inizialmente nel contenitore, con il frammento C nascosto all'inizio tramite un'operazione di hide()transazione. Abbiamo quindi show()C e hide()A quando si verifica l'evento UI. Tuttavia, non vedo come ciò gestisca il fatto che B cambi dimensioni. Si basa anche sul fatto che apparentemente è possibile aggiungere più frammenti allo stesso contenitore e non sono sicuro che si tratti di un comportamento affidabile a lungo termine (per non parlare del fatto che dovrebbe rompersi findFragmentById(), anche se posso conviverci).


L'autore di questo post sul blog indica che Gmail non utilizza setCustomAnimations()affatto, ma utilizza direttamente direttamente gli animatori di oggetti ("basta cambiare il margine sinistro della vista radice + cambiare la larghezza della vista destra"). Tuttavia, questa è ancora una soluzione AFAICT a due frammenti e l'implementazione ha mostrato ancora una volta le dimensioni dei fili rigidi in pixel.


Continuerò a collegarmi a questo, quindi un giorno potrei finire per rispondere a questa domanda, ma spero davvero che qualcuno abbia elaborato la soluzione a tre frammenti per questo scenario di animazione e possa pubblicare il codice (o un link ad esso). Le animazioni in Android mi fanno venir voglia di togliermi i capelli e quelli di voi che mi hanno visto sanno che si tratta di uno sforzo ampiamente inutile.


2
Non è qualcosa del genere pubblicato da Romain Guy? curious-creature.org/2011/02/22/…
Fernando Gallego,

1
@ ferdy182: non usa frammenti AFAICT. Visivamente, è il giusto effetto di base e potrei essere in grado di usarlo come un altro punto dati per provare a creare una risposta basata su frammenti. Grazie!
CommonsWare,

1
Solo un pensiero: l'app di posta elettronica standard ha un comportamento simile (anche se non del tutto identico) sul mio Nexus 7, che dovrebbe essere open source.
Christopher Orr,

4
L'app Email utilizza un "ThreePaneLayout" personalizzato che anima la posizione della vista a sinistra e la larghezza / visibilità delle altre due viste, ciascuna delle quali contiene il frammento pertinente.
Christopher Orr,

2
hey @CommonsWare Non mi preoccuperò per la mia intera risposta qui, ma c'era una domanda simile alla tua in cui ho dato una risposta molto soddisfacente. Così, solo suggerimento che per il check-out (controllare anche i commenti): stackoverflow.com/a/14155204/906362
Budius

Risposte:


67

Ho caricato la mia proposta su github ( Funziona con tutte le versioni di Android anche se la visualizzazione dell'accelerazione hardware è fortemente consigliata per questo tipo di animazioni. Per i dispositivi con accelerazione non hardware, l'implementazione della cache bitmap dovrebbe adattarsi meglio)

Il video dimostrativo con l'animazione è qui (causa del frame rate lento del cast dello schermo. Le prestazioni effettive sono molto veloci)


Uso:

layout = new ThreeLayout(this, 3);
layout.setAnimationDuration(1000);
setContentView(layout);
layout.getLeftView();   //<---inflate FragmentA here
layout.getMiddleView(); //<---inflate FragmentB here
layout.getRightView();  //<---inflate FragmentC here

//Left Animation set
layout.startLeftAnimation();

//Right Animation set
layout.startRightAnimation();

//You can even set interpolators

Spiegazione :

Creato un nuovo RelativeLayout personalizzato (ThreeLayout) e 2 animazioni personalizzate (MyScalAnimation, MyTranslateAnimation)

ThreeLayoutottiene il peso del riquadro sinistro come parametro, supponendo che abbia l'altra vista visibile weight=1.

Quindi new ThreeLayout(context,3)crea una nuova vista con 3 figli e il riquadro sinistro con 1/3 dello schermo totale. L'altra vista occupa tutto lo spazio disponibile.

Calcola la larghezza in fase di esecuzione, un'implementazione più sicura è che le dimensioni vengono calcolate la prima volta in draw (). anziché in post ()

Ridimensionare e tradurre le animazioni effettivamente ridimensiona e sposta la vista e non pseudo- [ridimensiona, sposta]. Si noti che fillAfter(true)non viene utilizzato da nessuna parte.

View2 è right_of View1

e

View3 è right_of View2

Avendo impostato queste regole RelativeLayout si occupa di tutto il resto. Le animazioni modificano margins(in movimento) e [width,height]in scala

Per accedere a ciascun bambino (in modo da poterlo gonfiare con il tuo frammento, puoi chiamare

public FrameLayout getLeftLayout() {}

public FrameLayout getMiddleLayout() {}

public FrameLayout getRightLayout() {}

Di seguito sono illustrate le 2 animazioni


Fase 1

--- Schermata IN ----------! ----- OUT ----

[Visualizzazione1] [_____ View2 _____] [_____ View3_____]

Fase 2

--OUT -! -------- NELLA schermata ------

[Visualizzazione1] [View2] [_____ View3_____]


1
Anche se non ho ancora implementato un animatore di scala, sono preoccupato che funzionerà bene per solidi campi di colore e meno bene, diciamo, contenuti "reali". I risultati del frammento B vengono ridimensionati per adattarsi allo spazio del frammento A non dovrebbe essere diverso rispetto a se il frammento B fosse stato collocato lì originariamente. Ad esempio, AFAIK, un animatore di ridimensionamento ridimensionerà la dimensione del carattere (disegnando tutto nel Viewpiù piccolo animato ), ma in Gmail / Email non otteniamo caratteri più piccoli, solo meno parole.
Commons War

1
@CommonsWare scaleAnimation è solo un nome astratto. In effetti, non viene eseguito alcun ridimensionamento. In realtà ridimensiona la larghezza della vista. Controlla la fonte. LayoutResizer potrebbe essere un nome migliore per questo.
debole

1
Questa non è la mia interpretazione della fonte attorno al scaleXvalore View, sebbene io possa interpretare male le cose.
Commons War

1
@CommonsWare controlla questo github.com/weakwire/3PaneLayout/blob/master/src/com/example/… fuori. L'unica ragione per cui estendo l'animazione è ottenere l'accesso a InterpolatedTime. p.width = (int) larghezza; fa tutta la magia e non è scaleX. Sono le dimensioni effettive della vista che cambiano durante il tempo specificato.
debole

2
Il tuo video mostra un buon framerate, ma le visualizzazioni utilizzate nel tuo esempio sono molto semplici. Fare un requestLayout () durante un'animazione è molto costoso. Soprattutto se i bambini sono complessi (ad esempio un ListView). Ciò comporterà probabilmente un'animazione instabile. L'app GMail non chiama requestLayout. Invece, un'altra vista più piccola viene inserita nel pannello centrale appena prima che inizi l'animazione.
Thierry-Dimitri Roy,

31

OK, ecco la mia soluzione, derivata dall'app Email AOSP, secondo il suggerimento di @ Christopher nei commenti della domanda.

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePane

La soluzione di @wirewire ricorda la mia, sebbene usi il classico Animationpiuttosto che gli animatori e usa le RelativeLayoutregole per imporre il posizionamento. Dal punto di vista della generosità, probabilmente otterrà la generosità, a meno che qualcun altro con una soluzione più semplice abbia ancora pubblicato una risposta.


In poche parole, il ThreePaneLayoutprogetto in questione è una LinearLayoutsottoclasse, progettata per funzionare nel paesaggio con tre bambini. Le larghezze di quei bambini possono essere impostate nell'XML del layout, con qualunque mezzo desiderato - io mostro usando i pesi, ma potresti avere larghezze specifiche impostate dalle risorse di dimensione o altro. Il terzo figlio - Frammento C nella domanda - dovrebbe avere una larghezza pari a zero.

package com.commonsware.android.anim.threepane;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class ThreePaneLayout extends LinearLayout {
  private static final int ANIM_DURATION=500;
  private View left=null;
  private View middle=null;
  private View right=null;
  private int leftWidth=-1;
  private int middleWidthNormal=-1;

  public ThreePaneLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initSelf();
  }

  void initSelf() {
    setOrientation(HORIZONTAL);
  }

  @Override
  public void onFinishInflate() {
    super.onFinishInflate();

    left=getChildAt(0);
    middle=getChildAt(1);
    right=getChildAt(2);
  }

  public View getLeftView() {
    return(left);
  }

  public View getMiddleView() {
    return(middle);
  }

  public View getRightView() {
    return(right);
  }

  public void hideLeft() {
    if (leftWidth == -1) {
      leftWidth=left.getWidth();
      middleWidthNormal=middle.getWidth();
      resetWidget(left, leftWidth);
      resetWidget(middle, middleWidthNormal);
      resetWidget(right, middleWidthNormal);
      requestLayout();
    }

    translateWidgets(-1 * leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", middleWidthNormal,
                         leftWidth).setDuration(ANIM_DURATION).start();
  }

  public void showLeft() {
    translateWidgets(leftWidth, left, middle, right);

    ObjectAnimator.ofInt(this, "middleWidth", leftWidth,
                         middleWidthNormal).setDuration(ANIM_DURATION)
                  .start();
  }

  public void setMiddleWidth(int value) {
    middle.getLayoutParams().width=value;
    requestLayout();
  }

  private void translateWidgets(int deltaX, View... views) {
    for (final View v : views) {
      v.setLayerType(View.LAYER_TYPE_HARDWARE, null);

      v.animate().translationXBy(deltaX).setDuration(ANIM_DURATION)
       .setListener(new AnimatorListenerAdapter() {
         @Override
         public void onAnimationEnd(Animator animation) {
           v.setLayerType(View.LAYER_TYPE_NONE, null);
         }
       });
    }
  }

  private void resetWidget(View v, int width) {
    LinearLayout.LayoutParams p=
        (LinearLayout.LayoutParams)v.getLayoutParams();

    p.width=width;
    p.weight=0;
  }
}

Tuttavia, in fase di esecuzione, indipendentemente dal modo in cui hai impostato originariamente le larghezze, la gestione della larghezza viene acquisita ThreePaneLayoutla prima volta che utilizzi hideLeft()per passare dalla visualizzazione dei frammenti A e B ai frammenti B e C. Nella terminologia di ThreePaneLayout- che non ha legami particolari con frammenti - i tre pezzi sono left, middlee right. Al momento si chiama hideLeft(), registriamo le dimensioni delle lefte middlee azzerare i pesi che sono stati utilizzati in qualsiasi dei tre, in modo che possiamo controllare completamente le dimensioni. Al momento hideLeft(), impostiamo la dimensione di rightessere la dimensione originale di middle.

Le animazioni sono duplici:

  • Utilizzare a ViewPropertyAnimatorper eseguire una traduzione dei tre widget a sinistra della larghezza di left, usando un livello hardware
  • Utilizzare una ObjectAnimatorpseudo-proprietà personalizzata di middleWidthper modificare la middlelarghezza da qualunque cosa abbia iniziato con la larghezza originale dileft

(è possibile che sia una migliore idea usare un AnimatorSete ObjectAnimatorsper tutti questi, anche se per ora funziona)

(è anche possibile che middleWidth ObjectAnimatornega il valore del livello hardware, poiché ciò richiede un invalidamento abbastanza continuo)

sicuramente possibile che io abbia ancora delle lacune nella comprensione dell'animazione e che mi piacciano le dichiarazioni tra parentesi)

L'effetto netto è che leftscorre fuori dallo schermo, middlescorre nella posizione e nelle dimensioni originali lefte si righttraduce proprio dietro middle.

showLeft() inverte semplicemente il processo, con lo stesso mix di animatori, solo con le direzioni invertite.

L'attività utilizza a ThreePaneLayoutper contenere una coppia di ListFragmentwidget e a Button. La selezione di qualcosa nel frammento di sinistra aggiunge (o aggiorna il contenuto di) il frammento di mezzo. Selezionando qualcosa nel frammento centrale si imposta la didascalia di Button, più viene eseguita hideLeft()su ThreePaneLayout. Premendo INDIETRO, se nascondiamo il lato sinistro, verrà eseguito showLeft(); in caso contrario, BACK esce dall'attività. Dal momento che questo non viene utilizzato FragmentTransactionsper influire sulle animazioni, siamo bloccati a gestire da soli lo "stack posteriore".

Il progetto collegato sopra utilizza frammenti nativi e il framework di animatore nativo. Ho un'altra versione dello stesso progetto che utilizza il backport dei frammenti del supporto Android e NineOldAndroids per l'animazione:

https://github.com/commonsguy/cw-omnibus/tree/master/Animation/ThreePaneBC

Il backport funziona bene su un Kindle Fire di prima generazione, sebbene l'animazione sia un po 'a scatti, date le specifiche hardware inferiori e la mancanza di supporto per l'accelerazione hardware. Entrambe le implementazioni sembrano fluide su un Nexus 7 e su altri tablet di ultima generazione.

Sono sicuramente aperto a idee su come migliorare questa soluzione o altre soluzioni che offrono chiari vantaggi rispetto a ciò che ho fatto qui (o a ciò che @weakwire ha usato).

Grazie ancora a tutti coloro che hanno contribuito!


1
Ciao! Grazie per la soluzione, ma penso che l'aggiunta di v.setLayerType () a volte renda le cose un po 'più lente. Rimuovi solo queste linee (e anche Listener) Questo è almeno vero per i dispositivi Android 3+ che ho appena provato su Galaxy Nexus e senza queste linee funziona molto meglio
Evgeny Nacu

1
"Entrambe le implementazioni sembrano fluide su un Nexus 7 e su altri tablet di ultima generazione." Ti credo; ma sto testando la tua implementazione con ObjectAnimator nativo su un Nexus 7 e è un po 'in ritardo. Quali sarebbero le cause più probabili? Ho già hardware accelerato: vero in AndroidMAnifest. Davvero non so quale potrebbe essere il problema. Ho anche provato la variante di DanielGrech e rimane indietro. Riesco a vedere differenze nell'animazione (la sua animazione si basa sui pesi), ma non riesco a capire perché sia ​​in ritardo in entrambi i casi.
Bogdan Zurac,

1
@Andrew: "è un po 'in ritardo" - Non sono sicuro di come tu stia usando il verbo "lag" qui. Per me, "lag" implica un obiettivo concreto per il confronto. Quindi, ad esempio, un'animazione legata a un movimento di scorrimento potrebbe rimanere indietro rispetto al movimento del dito. In questo caso, tutto è attivato dai tocchi, e quindi non sono chiaro cosa l'animazione potrebbe essere in ritardo. Indovinare che forse "ritardi un po '" significa semplicemente "sembra lento", non l'ho visto, ma non pretendo di avere un forte senso estetico, e quindi quale potrebbe essere un'animazione accettabile per me potrebbe essere inaccettabile per te.
CommonsWare

2
@CommonsWare: ho compilato il tuo codice con 4.2, ottimo lavoro. Quando ho toccato il centro ListViewe ho premuto il pulsante indietro rapidamente e ripetuto, l'intero layout si è spostato a destra. Ha iniziato a mostrare una colonna di spazio extra a sinistra. Questo comportamento è stato introdotto perché non avrei lasciato completare la prima animazione (avviata toccando al centro ListView) e premendo il pulsante Indietro. L'ho risolto sostituendo translationXBycon translationXin translateWidgetsmethod e translateWidgets(leftWidth, left, middle, right);con translateWidgets(0, left, middle, right);in showLeft()method.
M-WaJeEh,

2
Ho anche sostituito requestLayout();con middle.requestLayout();in setMiddleWidth(int value);metodo. Potrebbe non influire sulle prestazioni in quanto suppongo che la GPU eseguirà comunque un passaggio di layout completo, eventuali commenti?
M-WaJeEh,

13

Abbiamo creato una libreria chiamata PanesLibrary che risolve questo problema. È ancora più flessibile di quanto offerto in precedenza perché:

  • Ogni riquadro può essere dimensionato dinamicamente
  • Consente qualsiasi numero di riquadri (non solo 2 o 3)
  • I frammenti all'interno dei riquadri vengono mantenuti correttamente nei cambi di orientamento.

Puoi verificarlo qui: https://github.com/Mapsaurus/Android-PanesLibrary

Ecco una demo: http://www.youtube.com/watch?v=UA-lAGVXoLU&feature=youtu.be

Fondamentalmente ti consente di aggiungere facilmente qualsiasi numero di riquadri di dimensioni dinamiche e collegare frammenti a tali riquadri. Spero che lo trovi utile! :)


6

Sulla base di uno degli esempi a cui hai collegato ( http://android.amberfog.com/?p=758 ), che ne dici di animare la layout_weightproprietà? In questo modo, puoi animare insieme la variazione di peso dei 3 frammenti E ottieni il bonus che scivolano tutti insieme bene:

Inizia con un layout semplice. Dal momento che stiamo per animare layout_weight, abbiamo bisogno di una LinearLayoutvista radice per i 3 pannelli .:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:id="@+id/panel1"
    android:layout_width="0dip"
    android:layout_weight="1"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel2"
    android:layout_width="0dip"
    android:layout_weight="2"
    android:layout_height="match_parent"/>

<LinearLayout
    android:id="@+id/panel3"
    android:layout_width="0dip"
    android:layout_weight="0"
    android:layout_height="match_parent"/>
</LinearLayout>

Quindi la classe demo:

public class DemoActivity extends Activity implements View.OnClickListener {
    public static final int ANIM_DURATION = 500;
    private static final Interpolator interpolator = new DecelerateInterpolator();

    boolean isCollapsed = false;

    private Fragment frag1, frag2, frag3;
    private ViewGroup panel1, panel2, panel3;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        panel1 = (ViewGroup) findViewById(R.id.panel1);
        panel2 = (ViewGroup) findViewById(R.id.panel2);
        panel3 = (ViewGroup) findViewById(R.id.panel3);

        frag1 = new ColorFrag(Color.BLUE);
        frag2 = new InfoFrag();
        frag3 = new ColorFrag(Color.RED);

        final FragmentManager fm = getFragmentManager();
        final FragmentTransaction trans = fm.beginTransaction();

        trans.replace(R.id.panel1, frag1);
        trans.replace(R.id.panel2, frag2);
        trans.replace(R.id.panel3, frag3);

        trans.commit();
    }

    @Override
    public void onClick(View view) {
        toggleCollapseState();
    }

    private void toggleCollapseState() {
        //Most of the magic here can be attributed to: http://android.amberfog.com/?p=758

        if (isCollapsed) {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 0.0f, 1.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 1.0f, 2.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 2.0f, 0.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        } else {
            PropertyValuesHolder[] arrayOfPropertyValuesHolder = new PropertyValuesHolder[3];
            arrayOfPropertyValuesHolder[0] = PropertyValuesHolder.ofFloat("Panel1Weight", 1.0f, 0.0f);
            arrayOfPropertyValuesHolder[1] = PropertyValuesHolder.ofFloat("Panel2Weight", 2.0f, 1.0f);
            arrayOfPropertyValuesHolder[2] = PropertyValuesHolder.ofFloat("Panel3Weight", 0.0f, 2.0f);
            ObjectAnimator localObjectAnimator = ObjectAnimator.ofPropertyValuesHolder(this, arrayOfPropertyValuesHolder).setDuration(ANIM_DURATION);
            localObjectAnimator.setInterpolator(interpolator);
            localObjectAnimator.start();
        }
        isCollapsed = !isCollapsed;
    }

    @Override
    public void onBackPressed() {
        //TODO: Very basic stack handling. Would probably want to do something relating to fragments here..
        if(isCollapsed) {
            toggleCollapseState();
        } else {
            super.onBackPressed();
        }
    }

    /*
     * Our magic getters/setters below!
     */

    public float getPanel1Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     panel1.getLayoutParams();
        return params.weight;
    }

    public void setPanel1Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel1.getLayoutParams();
        params.weight = newWeight;
        panel1.setLayoutParams(params);
    }

    public float getPanel2Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        return params.weight;
    }

    public void setPanel2Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel2.getLayoutParams();
        params.weight = newWeight;
        panel2.setLayoutParams(params);
    }

    public float getPanel3Weight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        return params.weight;
    }

    public void setPanel3Weight(float newWeight) {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) panel3.getLayoutParams();
        params.weight = newWeight;
        panel3.setLayoutParams(params);
    }


    /**
     * Crappy fragment which displays a toggle button
     */
    public static class InfoFrag extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle     savedInstanceState) {
            LinearLayout layout = new LinearLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            layout.setBackgroundColor(Color.DKGRAY);

            Button b = new Button(getActivity());
            b.setOnClickListener((DemoActivity) getActivity());
            b.setText("Toggle Me!");

            layout.addView(b);

            return layout;
        }
    }

    /**
     * Crappy fragment which just fills the screen with a color
     */
    public static class ColorFrag extends Fragment {
        private int mColor;

        public ColorFrag() {
            mColor = Color.BLUE; //Default
        }

        public ColorFrag(int color) {
            mColor = color;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            FrameLayout layout = new FrameLayout(getActivity());
            layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

            layout.setBackgroundColor(mColor);
            return layout;
        }
    }
}

Anche questo esempio non usa FragmentTransactions per realizzare le animazioni (anzi, anima le viste a cui sono collegati i frammenti), quindi dovresti fare tu stesso tutte le transazioni di backstack / frammento, ma rispetto allo sforzo di far funzionare le animazioni bene, questo non sembra un cattivo compromesso :)

Video orribile a bassa risoluzione in azione: http://youtu.be/Zm517j3bFCo


1
Ebbene, Gmail fa sicuramente una definizione di quello che descrivo come Frammento A. avrei preoccuparsi che riducendo il peso a 0 potrebbe introdurre artefatti visivi (ad esempio, TextViewcon wrap_contentallungamento si verticalmente al procedere animazione). Tuttavia, è possibile che animare il peso sia il modo in cui ridimensionano quello che ho definito Frammento B. Grazie!
Commons War

1
Nessun problema! Questo è un buon punto, tuttavia, ciò probabilmente causerebbe artefatti visivi a seconda delle viste dei bambini nel 'Frammento A'. Speriamo che qualcuno trovi un modo più solido per farlo
DanielGrech,

5

Questo non utilizza frammenti ... È un layout personalizzato con 3 figli. Quando si fa clic su un messaggio, si compensano i 3 bambini usando offsetLeftAndRight()e un animatore.

In JellyBeanpuoi abilitare "Mostra limiti di layout" nelle impostazioni "Opzioni Developper". Quando l'animazione della diapositiva è completa, puoi ancora vedere che il menu di sinistra è ancora lì, ma sotto il pannello centrale.

È simile al menu dell'app Fly-in di Cyril Mottier , ma con 3 elementi anziché 2.

Inoltre, il ViewPagerterzo dei figli è un'altra indicazione di questo comportamento: di ViewPagersolito usa Frammenti (so che non è necessario, ma non ho mai visto un'implementazione diversa da quella Fragment), e dato che non puoi usare Fragmentsall'interno di un altro Fragmenti 3 figli probabilmente non sono frammenti ....


Non ho mai usato "Mostra limiti di layout" prima, il che è molto utile per le app a sorgente chiuso come questa (grazie!). Sembra che quello che ho descritto come Frammento A stia traducendo fuori dallo schermo (puoi vedere la sua diapositiva del bordo destro da destra a sinistra), prima di tornare indietro sotto quello che ho descritto come Frammento B. Mi sembra un TranslateAnimation, sebbene io sia sicuro che ci sono modi di usare gli animatori per raggiungere gli stessi fini. Dovrò fare alcuni esperimenti su questo. Grazie!
CommonsWare,

2

Attualmente sto provando a fare qualcosa del genere, tranne che la scala del frammento B per occupare lo spazio disponibile e che il riquadro 3 può essere aperto contemporaneamente se c'è abbastanza spazio. Qui è la mia soluzione finora, ma non sono sicuro che ci riuscirò. Spero che qualcuno fornisca una risposta che mostri la strada giusta.

Invece di usare un LinearLayout e di animare il peso, uso un RelativeLayout e anima i margini. Non sono sicuro che sia il modo migliore perché richiede una chiamata a requestLayout () ad ogni aggiornamento. È fluido su tutti i miei dispositivi però.

Quindi, anima il layout, non sto usando la transazione di frammenti. Gestisco manualmente il pulsante Indietro per chiudere il frammento C se è aperto.

Frammento B usa layout_toLeftOf / ToRightOf per mantenerlo allineato al frammento A e C.

Quando la mia app attiva un evento per visualizzare il frammento C, faccio scorrere il frammento C e allo stesso tempo faccio scorrere il frammento A. (2 animazioni separate). Inversamente, quando il frammento A si apre, chiudo C contemporaneamente.

In modalità verticale o su schermo più piccolo, utilizzo un layout leggermente diverso e faccio scorrere il frammento C sullo schermo.

Per utilizzare la percentuale per la larghezza del frammento A e C, penso che dovresti calcolarlo in fase di esecuzione ... (?)

Ecco il layout dell'attività:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rootpane"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <!-- FRAGMENT A -->
    <fragment
        android:id="@+id/fragment_A"
        android:layout_width="300dp"
        android:layout_height="fill_parent"
        class="com.xyz.fragA" />

    <!-- FRAGMENT C -->
    <fragment
        android:id="@+id/fragment_C"
        android:layout_width="600dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"
        class="com.xyz.fragC"/>

    <!-- FRAGMENT B -->
    <fragment
        android:id="@+id/fragment_B"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_marginLeft="0dip"
        android:layout_marginRight="0dip"
        android:layout_toLeftOf="@id/fragment_C"
        android:layout_toRightOf="@id/fragment_A"
        class="com.xyz.fragB" />

</RelativeLayout>

L'animazione per far scorrere FragmentC dentro o fuori:

private ValueAnimator createFragmentCAnimation(final View fragmentCRootView, boolean slideIn) {

    ValueAnimator anim = null;

    final RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) fragmentCRootView.getLayoutParams();
    if (slideIn) {
        // set the rightMargin so the view is just outside the right edge of the screen.
        lp.rightMargin = -(lp.width);
        // the view's visibility was GONE, make it VISIBLE
        fragmentCRootView.setVisibility(View.VISIBLE);
        anim = ValueAnimator.ofInt(lp.rightMargin, 0);
    } else
        // slide out: animate rightMargin until the view is outside the screen
        anim = ValueAnimator.ofInt(0, -(lp.width));

    anim.setInterpolator(new DecelerateInterpolator(5));
    anim.setDuration(300);
    anim.addUpdateListener(new AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Integer rightMargin = (Integer) animation.getAnimatedValue();
            lp.rightMargin = rightMargin;
            fragmentCRootView.requestLayout();
        }
    });

    if (!slideIn) {
        // if the view was sliding out, set visibility to GONE once the animation is done
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                fragmentCRootView.setVisibility(View.GONE);
            }
        });
    }
    return anim;
}
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.