2012-10-22 12 views
55

Sto cercando di ottenere il seguente effetto utilizzando FragmentTransaction.setCustomAnimations.Animazione FrammentTransaction per scorrere sopra

  1. frammento A mostra
  2. Sostituire frammento A con frammento B. frammento A deve rimanere visibile durante la sostituzione. Il frammento B dovrebbe entrare da destra. Il frammento B dovrebbe scorrere in OVER THE TOP del frammento A.

Non ho problemi a ottenere la diapositiva nell'impostazione dell'animazione. Il mio problema è che non riesco a capire come rendere Framment A rimanere dov'è ed essere UNDER Fragment B mentre la diapositiva in animazione è in esecuzione. A prescindere da quello che faccio, sembra che il frammento A sia al top.

Come posso ottenere questo risultato?

ecco il codice FragmentTransaction:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.nothing, R.anim.nothing, 
    R.anim.slide_out_right); 
ft.replace(R.id.fragment_content, fragment, name); 
ft.addToBackStack(name); 
ft.commit(); 

Come potete vedere ho definito un'animazione R.anim.nothing per l'animazione "out", perché io in realtà non voglio frammento A fare altro di rimanere dove è durante la transazione.

Qui ci sono le risorse di animazione:

slide_in_right.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android" 
    android:duration="@android:integer/config_mediumAnimTime" 
    android:fromXDelta="100%p" 
    android:toXDelta="0" 
    android:zAdjustment="top" /> 

nothing.xml

<alpha xmlns:android="http://schemas.android.com/apk/res/android" 
    android:duration="@android:integer/config_mediumAnimTime" 
    android:fromAlpha="1.0" 
    android:toAlpha="1.0" 
    android:zAdjustment="bottom" /> 
+1

Ho archiviato un bug report perché l'ordinamento Z corrente è semplicemente sbagliato. https://code.google.com/p/android/issues/detail?id=163384&thanks=163384&ts=1428443402 – sbaar

risposta

9

ho trovato una soluzione che funziona per me. Ho finito per utilizzare un ViewPager con FragmentStatePagerAdapter. ViewPager fornisce il comportamento di scorrimento e gli swap di FragmentStatePagerAdapter nei frammenti. Il trucco finale per ottenere l'effetto di avere una pagina visibile "sotto" la pagina in arrivo consiste nell'utilizzare un PageTransformer. PageTransformer sovrascrive la transizione predefinita di ViewPager tra le pagine. Ecco un esempio di PageTransformer che ottiene l'effetto con la traduzione e una piccola quantità di ridimensionamento nella pagina sinistra.

public class ScalePageTransformer implements PageTransformer { 
    private static final float SCALE_FACTOR = 0.95f; 

    private final ViewPager mViewPager; 

    public ScalePageTransformer(ViewPager viewPager) { 
      this.mViewPager = viewPager; 
    } 

    @SuppressLint("NewApi") 
    @Override 
    public void transformPage(View page, float position) { 
     if (position <= 0) { 
      // apply zoom effect and offset translation only for pages to 
      // the left 
      final float transformValue = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR) + SCALE_FACTOR; 
      int pageWidth = mViewPager.getWidth(); 
      final float translateValue = position * -pageWidth; 
      page.setScaleX(transformValue); 
      page.setScaleY(transformValue); 
      if (translateValue > -pageWidth) { 
       page.setTranslationX(translateValue); 
      } else { 
       page.setTranslationX(0); 
      } 
     } 
    } 

} 
+0

Ottima soluzione, ma sfortunatamente non si adatta se si desidera modificare lo stack, specialmente nella direzione della diminuzione: ciò comporta l'abbagliamento (ridisegnando l'intero elemento). –

20

Non so se hai ancora bisogno di una risposta, ma di recente ho bisogno di fare lo stesso e ho trovato un modo per fare quello che vuoi.

ho fatto qualcosa di simile:

FragmentManager fm = getFragmentManager(); 
FragmentTransaction ft = fm.beginTransaction(); 

MyFragment next = getMyFragment(); 

ft.add(R.id.MyLayout,next); 
ft.setCustomAnimations(R.anim.slide_in_right,0); 
ft.show(next); 
ft.commit(); 

visualizzo il mio frammento in un FrameLayout.

Funziona multe ma il vecchio frammento è ancora a mio avviso, ho lasciato Android gestirlo come vuole, perché se metto:

ft.remove(myolderFrag); 

esso non viene visualizzato durante l'animazione.

slide_in_right.xml

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android" > 
<translate android:duration="150" android:fromXDelta="100%p" 
android:interpolator="@android:anim/linear_interpolator" 
android:toXDelta="0" /> 
</set> 
+0

Grazie e sto ancora cercando una soluzione. La tua risposta è accurata ma sfortunatamente non risolve completamente il problema. Non posso lasciare in memoria il vecchio frammento o mangerà troppa memoria. Ho preso l'approccio di creare una bitmap dalla vista in uscita e posizionandola sotto la vista che il nuovo frammento è stato aggiunto. Ciò raggiunge l'effetto, ma di nuovo ho problemi di memoria perché la bitmap è troppo grande. Sto esaminando un paio di altri approcci tra cui ViewPager con FragmentStatePagerAdapter e un FrameLayout per frammento. Pubblicheremo più tardi. –

+1

@ domji84 ' ' – Meph

+0

Grazie. 'overridePendingTransition (R.anim.my_animation, 0)' è la linea che stavo cercando. –

2

Ho trovato una soluzione alternativa (non pesantemente testato) che trovo più elegante rispetto alle proposte finora:

final IncomingFragment newFrag = new IncomingFragment(); 
newFrag.setEnterAnimationListener(new Animation.AnimationListener() { 
       @Override 
       public void onAnimationStart(Animation animation) { 

       } 

       @Override 
       public void onAnimationEnd(Animation animation) { 
        clearSelection(); 
        inFrag.clearEnterAnimationListener(); 

        getFragmentManager().beginTransaction().remove(OutgoingFragment.this).commit(); 
       } 

       @Override 
       public void onAnimationRepeat(Animation animation) { 

       } 
      }); 

getActivity().getSupportFragmentManager().beginTransaction() 
        .setCustomAnimations(R.anim.slide_in_from_right, 0) 
        .add(R.id.container, inFrag) 
        .addToBackStack(null) 
        .commit(); 

Questo viene chiamato dall'interno di una classe interna della classe OutgoingFragment.

Un nuovo frammento viene inserito, l'animazione è completa, quindi il vecchio frammento viene rimosso.

Ci possono essere alcuni problemi di memoria con questo in alcune applicazioni, ma è meglio che conservare entrambi i frammenti indefinitamente.

+0

Per me questa è la soluzione migliore finora. Ero solito fare lo stesso ma mettendo in pausa il sistema con un Handler durante l'animazione e poi rimuovendo il frammento precedente, ma questo è molto meglio. – JDenais

+0

Da dove provengono le classi IncomingFragment e OutgoingFragment? Sono personalizzati? Non capisco perfettamente l'implementazione alla base di questo. Forse quando è stato scritto c'era una funzione come 'Fragment.setEnterAnimationListener (...)' ... o è una funzione personalizzata per quel frammento? – NineToeNerd

4

Dopo ulteriori sperimentazioni (quindi questa è la mia seconda risposta), il problema sembra essere che R.anim.nothing significa 'scomparire' quando vogliamo un'altra animazione che dice esplicitamente 'stay put'. La soluzione è quella di definire una vera e propria animazione 'non fare nulla' in questo modo:

make file no_animation.xml:

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android"> 
    <scale 
     android:interpolator="@android:anim/linear_interpolator" 
     android:fromXScale="1.0" 
     android:toXScale="1.0" 
     android:fromYScale="1.0" 
     android:toYScale="1.0" 
     android:duration="200" 
     /> 
    <alpha xmlns:android="http://schemas.android.com/apk/res/android" 
     android:fromAlpha="1.0" 
     android:toAlpha="0.0" 
     android:duration="200" 
     android:startOffset="200" 
     /> 
</set> 

Ora basta fare come si farebbe altrimenti:

getActivity().getSupportFragmentManager().beginTransaction() 
       .setCustomAnimations(R.anim.slide_in_right, R.anim.no_animation) 
       .replace(R.id.container, inFrag, FRAGMENT_TAG) 
       .addToBackStack("Some text") 
       .commit(); 
+0

Proverò questo, ma ho pensato di fare un'animazione corretta "stay_put" e tutto ciò che ho fatto è stato coprire l'animazione scorrevole del nuovo frammento con il vecchio frammento che rimane in primo piano. Ma ti vedo compensare l'inizio della no_animation, quindi forse fa il trucco. Ti farò sapere cosa penso della tua soluzione. – JDenais

+1

Bel pensiero ma questo non funziona mentre la transazione rimuove istantaneamente il frammento e posso vedere lo schermo bianco sotto il secondo frammento durante l'animazione. –

+0

Confuso sul motivo per cui hai pensato che fosse necessario. Ho appena usato 0 dove non volevo transizione e ha funzionato alla grande. Qual è l'aspetto che hai fatto sopra. – NineToeNerd

11

Partendo da Lollipop, è può aumentare de translationZ del tuo frammento in entrata. Apparirà sopra l'exiting.

Ad esempio:

@Override 
public void onViewCreated(View view, Bundle savedInstanceState) { 
    super.onViewCreated(view, savedInstanceState); 
    ViewCompat.setTranslationZ(getView(), 100.f); 
} 

Se si desidera modificare il valore translationZ solo per la durata dell'animazione, si dovrebbe fare qualcosa di simile:

@Override 
public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) { 
    Animation nextAnimation = AnimationUtils.loadAnimation(getContext(), nextAnim); 
    nextAnimation.setAnimationListener(new Animation.AnimationListener() { 

     private float mOldTranslationZ; 

     @Override 
     public void onAnimationStart(Animation animation) { 
      if (getView() != null && enter) { 
       mOldTranslationZ = ViewCompat.getTranslationZ(getView()); 
       ViewCompat.setTranslationZ(getView(), 100.f); 
      } 
     } 

     @Override 
     public void onAnimationEnd(Animation animation) { 
      if (getView() != null && enter) { 
       ViewCompat.setTranslationZ(getView(), mOldTranslationZ); 
      } 
     } 

     @Override 
     public void onAnimationRepeat(Animation animation) { 
     } 
    }); 
    return nextAnimation; 
} 
+0

Buon lavoro nel trovarlo, stavamo cercando questa soluzione tanto tempo fa e ora è stata risolta. –

+0

Grazie. Sembra funzionare. Posso sapere perché per l'override di 'onCreateAnimation', abbiamo bisogno di costruire manualmente l'animazione tramite' AnimationUtils.loadAnimation'? Mi rendo conto che se cerco di ottenere 'Animazione' tramite super.onCreateAnimation, è nullo. –

+0

@CheokYanCheng Perché il metodo 'Fragment.onCreateAnimation()' non esegue nulla per impostazione predefinita e restituisce semplicemente null. Questo metodo è solo lì per lasciarti la possibilità di creare te stesso l'animazione. Se date un'occhiata al metodo 'FragmentManager.loadAnimation()' vedrete che prima cerca di ottenere un'animazione da 'fragment.onCreateAnimation()' e se restituisce null FragmentManager crea l'animazione stessa. – JFrite

0
FragmentTransaction ft = ((AppCompatActivity) context).getSupportFragmentManager().beginTransaction(); 
       ft.setCustomAnimations(0, R.anim.slide_out_to_right); 
      if (!fragment.isAdded()) 
      { 
       ft.add(R.id.fragmentContainerFrameMyOrders, fragment); 
       ft.show(fragment); 
      } 
      else 
       ft.replace(R.id.fragmentContainerFrameMyOrders, fragment); 
      ft.commit(); 
0

Questo è il mio soluzione corrente per chiunque sia interessato.

Nella funzione per l'aggiunta del nuovo Fragment:

final Fragment toRemove = fragmentManager.findFragmentById(containerID); 
if (toRemove != null) { 
    new Handler().postDelayed(new Runnable() { 
      @Override 
      public void run() { 
       fragmentManager.beginTransaction().hide(toRemove).commit(); 
      } 
     }, 
     getResources().getInteger(android.R.integer.config_mediumAnimTime) + 100); 
     // Use whatever duration you chose for your animation for this handler 
     // I added an extra 100 ms because the first transaction wasn't always 
     // fast enough 
} 
fragmentManager.beginTransaction() 
    .setCustomAnimations(enter, 0, 0, popExit).add(containerID, fragmentToAdd) 
    .addToBackStack(tag).commit(); 

e in onCreate:

final FragmentManager fragmentManager = getSupportFragmentManager(); 
fragmentManager.addOnBackStackChangedListener(
     new FragmentManager.OnBackStackChangedListener() { 
      @Override 
      public void onBackStackChanged() { 
       Fragment current = fragmentManager.findFragmentById(containerID); 
       if (current != null && current.isHidden()) { 
        fragmentManager.beginTransaction().show(current).commit(); 
       } 
      } 
     }); 

Io preferirei una sorta di AnimationListener invece di un Handler sopra, ma non ho visto in qualsiasi modo puoi usarne uno per rilevare la fine dell'animazione della transazione che non era legata al frammento come onCreateAnimation(). Qualsiasi suggerimento/modifica con un ascoltatore appropriato sarebbe apprezzato.

Indicheremo gli Fragment s che sto aggiungendo in questo modo sono leggeri quindi non è un problema per me averli nel contenitore dei frammenti insieme al frammento su cui sono posizionati.

Se si desidera rimuovere il frammento si potrebbe mettere fragmentManager.beginTransaction().remove(toRemove).commitAllowingStateLoss(); nella s RunnableHandler', e nel OnBackStackChangedListener:

// Use back stack entry tag to get the fragment 
Fragment current = getCurrentFragment(); 
if (current != null && !current.isAdded()) { 
    fragmentManager.beginTransaction() 
     .add(containerId, current, current.getTag()) 
     .commitNowAllowingStateLoss(); 
} 

nota la soluzione di cui sopra non funziona per il primo frammento nel contenitore (perché non è nel back stack) quindi dovresti avere un altro modo per ripristinarlo, magari salvare un riferimento al primo frammento in qualche modo ... Ma se non usi il back stack e sostituisci sempre i frammenti manualmente , Questo non è un problema. OPPURE puoi aggiungere tutti i frammenti allo stack posteriore (incluso il primo) e sovrascrivere lo onBackPressed per assicurarti che l'attività esca invece di mostrare uno schermo vuoto quando rimane solo un frammento nello stack posteriore.

EDIT: Ho scoperto i seguenti funzioni che probabilmente potrebbe sostituire FragmentTransaction.remove() e FragmentTransaction.add() sopra:

FragmentTransaction. detach():

Staccare il frammento specificato dall'interfaccia utente. Questo è lo stesso stato di quando viene inserito nello stack posteriore: il frammento viene rimosso dall'interfaccia utente, tuttavia il suo stato viene ancora gestito attivamente dal gestore dei frammenti. Quando si entra in questo stato, la sua gerarchia di vista viene distrutta.

FragmentTransaction. attach():

Riattaccare un frammento dopo che era stato precedentemente rimosso dall'interfaccia utente con il distacco (frammento). Questo fa sì che la sua gerarchia delle viste venga ricreata, collegata all'interfaccia utente e visualizzata.

Problemi correlati