I frammenti nidificati scompaiono durante l'animazione della transizione


99

Ecco lo scenario: l'attività contiene frammento A, che a sua volta utilizza getChildFragmentManager()per aggiungere frammenti A1e A2in questo onCreatemodo:

getChildFragmentManager()
  .beginTransaction()
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

Fin qui tutto bene, tutto funziona come previsto.

Quindi eseguiamo la seguente transazione nell'attività:

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .replace(R.id.fragmentHolder, new FragmentB())
  .addToBackStack(null)
  .commit()

Durante la transizione, le enteranimazioni per il frammento vengono Beseguite correttamente ma i frammenti A1 e A2 scompaiono completamente . Quando ripristiniamo la transazione con il pulsante Indietro, vengono inizializzati correttamente e vengono visualizzati normalmente durante l' popEnteranimazione.

Nel mio breve test, è diventato più strano: se imposto le animazioni per i frammenti figlio (vedi sotto), l' exitanimazione viene eseguita in modo intermittente quando aggiungiamo frammentoB

getChildFragmentManager()
  .beginTransaction()
  .setCustomAnimations(enter, exit)
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

L'effetto che voglio ottenere è semplice: voglio che l' animazione exit(o dovrebbe essere popExit?) Sul frammento A(anim2) venga eseguita, animando l'intero contenitore, inclusi i suoi figli nidificati.

C'è un modo per ottenerlo?

Modifica : trova un caso di test qui

Edit2 : Grazie a @StevenByle per avermi spinto a continuare a provare con le animazioni statiche. Apparentemente puoi impostare le animazioni su una base per operazione (non globale per l'intera transazione), il che significa che i figli possono avere un set di animazioni statiche indefinite, mentre il loro genitore può avere un'animazione diversa e l'intera cosa può essere impegnata in una transazione . Vedere la discussione di seguito e il progetto del caso di test aggiornato .


Cosa c'è R.id.fragmentHolderrispetto ad A, A1, A2, ecc.?
CommonsWare

fragmentHolder è un id nel layout dell'attività, fragment {One, Two} Holder sono nel layout del frammento A. Tutti e tre sono distinti. Il frammento A è stato inizialmente aggiunto in fragmentHolder (ovvero, il frammento B sta sostituendo il frammento A).
Delyan

Ho creato un progetto di esempio qui: github.com/BurntBrunch/NestedFragmentsAnimationsTest , c'è anche un apk incluso nel repository. Questo è un bug davvero fastidioso e sto cercando un modo per aggirarlo (supponendo che non sia nel mio codice).
Delyan

Ora so qualcosa di più su questo problema. Il motivo per cui i frammenti scompaiono è perché i figli gestiscono gli eventi del ciclo di vita prima del genitore. In sostanza, A1 e A2 vengono rimossi prima di A e poiché non hanno le animazioni impostate, scompaiono bruscamente. Un modo per mitigare in qualche modo questo è rimuovere esplicitamente A1 e A2 nella transazione che sostituisce A. In questo modo, si animano quando escono, tuttavia la loro velocità di animazione è al quadrato, poiché anche il contenitore padre è animato. Una soluzione che non produca questo manufatto sarebbe apprezzata.
Delyan

La modifica (che sostituisce il frammento iniziale) che citi nella domanda è quella reale che vuoi fare o è solo un esempio? Chiamerai il changeFragmentmetodo solo una volta?
Luksprog

Risposte:


37

Per evitare che l'utente veda i frammenti annidati scomparire quando il frammento genitore viene rimosso / sostituito in una transazione, è possibile "simulare" quei frammenti ancora presenti fornendo un'immagine di essi, così come sono apparsi sullo schermo. Questa immagine verrà utilizzata come sfondo per il contenitore dei frammenti nidificati, quindi anche se le viste del frammento nidificato scompaiono, l'immagine simulerà la loro presenza. Inoltre, non vedo la perdita dell'interattività con le viste del frammento annidato come un problema perché non penso che vorresti che l'utente agisse su di esse quando sono solo in procinto di essere rimosse (probabilmente come un'azione dell'utente come bene).

Ho fatto un piccolo esempio con l'impostazione dell'immagine di sfondo (qualcosa di semplice).


1
Ho deciso di premiarti con la taglia, poiché quella era la soluzione che ho finito per usare. Grazie mille per il tuo tempo!
Delyan

17
Wow, così sporco. Le lunghezze che noi sviluppatori Android dobbiamo fare solo per un po 'di abilità
Dean Wild

1
Ho un Viewpager dalla seconda scheda sto sostituendo un altro frammento e quando lo premo di nuovo devo mostrare la seconda scheda di viewpager, si sta aprendo ma mostra una pagina vuota. Ho provato quello che hai suggerito nel thread sopra ma è ancora lo stesso.
Harish

Il problema rimane quando l'utente torna come ha scritto @Harish
Ewoks

1
Wow sul serio? è il 2018 e questa è ancora una cosa? :(
Archie G. Quiñones

69

Quindi sembra che ci siano molte soluzioni alternative per questo, ma sulla base della risposta di @ Jayd16, penso di aver trovato una soluzione abbastanza solida e universale che consente ancora animazioni di transizione personalizzate su frammenti figlio e non richiede di farlo una cache bitmap del layout.

Crea una BaseFragmentclasse che si estenda Fragmente fai in modo che tutti i tuoi frammenti estendano quella classe (non solo i frammenti figli).

In quella BaseFragmentclasse, aggiungi quanto segue:

// Arbitrary value; set it to some reasonable default
private static final int DEFAULT_CHILD_ANIMATION_DURATION = 250;

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    final Fragment parent = getParentFragment();

    // Apply the workaround only if this is a child fragment, and the parent
    // is being removed.
    if (!enter && parent != null && parent.isRemoving()) {
        // This is a workaround for the bug where child fragments disappear when
        // the parent is removed (as all children are first removed from the parent)
        // See https://code.google.com/p/android/issues/detail?id=55228
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else {
        return super.onCreateAnimation(transit, enter, nextAnim);
    }
}

private static long getNextAnimationDuration(Fragment fragment, long defValue) {
    try {
        // Attempt to get the resource ID of the next animation that
        // will be applied to the given fragment.
        Field nextAnimField = Fragment.class.getDeclaredField("mNextAnim");
        nextAnimField.setAccessible(true);
        int nextAnimResource = nextAnimField.getInt(fragment);
        Animation nextAnim = AnimationUtils.loadAnimation(fragment.getActivity(), nextAnimResource);

        // ...and if it can be loaded, return that animation's duration
        return (nextAnim == null) ? defValue : nextAnim.getDuration();
    } catch (NoSuchFieldException|IllegalAccessException|Resources.NotFoundException ex) {
        Log.w(TAG, "Unable to load next animation from parent.", ex);
        return defValue;
    }
}

Sfortunatamente, richiede una riflessione; tuttavia, poiché questa soluzione è per la libreria di supporto, non corri il rischio che l'implementazione sottostante cambi a meno che non aggiorni la libreria di supporto. Se stai creando la libreria di supporto dall'origine, potresti aggiungere una funzione di accesso per il successivo ID risorsa di animazione Fragment.javae rimuovere la necessità di riflessione.

Questa soluzione elimina la necessità di "indovinare" la durata dell'animazione del genitore (in modo che l'animazione "non fare nulla" abbia la stessa durata dell'animazione di uscita del genitore) e ti consente di eseguire comunque animazioni personalizzate sui frammenti figli (ad es. scambiando i frammenti del bambino con diverse animazioni).


5
Questa è la mia soluzione preferita nel thread. Non richiede una bitmap, non richiede alcun codice aggiuntivo nel frammento figlio e non perde realmente le informazioni dal padre al frammento figlio.
jacobhyphenated il

1
@EugenPechanec Hai bisogno del nextAnim dal frammento genitore , non quello del bambino. Questo è il punto.
Kevin Coppock

1
Sfortunatamente questo approccio causa una perdita di memoria per i frammenti figlio e implicitamente anche per il frammento genitore nelle versioni Android precedenti a Lollipop :(
Cosmin

8
Grazie per questa soluzione molto utile, tuttavia richiede un piccolo aggiornamento per funzionare con la libreria di supporto corrente (27.0.2, non so quale versione ha rotto questo codice). mNextAnimè ora all'interno di un mAnimationInfooggetto. Puoi accedervi in ​​questo modo:Field animInfoField = Fragment.class.getDeclaredField("mAnimationInfo"); animInfoField.setAccessible(true); Object animationInfo = animInfoField.get(fragment); Field nextAnimField = animationInfo.getClass().getDeclaredField("mNextAnim");
David Lericolais

5
@DavidLericolais, vorrei aggiungere un'altra riga di codice dopo la tua. val nextAnimResource = nextAnimField.getInt(animationInfo);per sostituire la lineaint nextAnimResource = nextAnimField.getInt(fragment);
tingyik90

32

Sono riuscito a trovare una soluzione abbastanza pulita. IMO è il meno hacky, e anche se tecnicamente questa è la soluzione "disegna una bitmap" almeno è astratta dal frammento lib.

Assicurati che tuo figlio frags sovrascriva una classe genitore con questo:

private static final Animation dummyAnimation = new AlphaAnimation(1,1);
static{
    dummyAnimation.setDuration(500);
}

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    if(!enter && getParentFragment() != null){
        return dummyAnimation;
    }
    return super.onCreateAnimation(transit, enter, nextAnim);
}

Se abbiamo un'animazione di uscita sui bambini frags, saranno animati invece di lampeggiare. Possiamo sfruttarlo disponendo di un'animazione che disegna semplicemente i frammenti figlio al massimo alfa per una durata. In questo modo, rimarranno visibili nel frammento genitore mentre si anima, dando il comportamento desiderato.

L'unico problema a cui riesco a pensare è tenere traccia di quella durata. Potrei forse impostarlo su un numero elevato, ma temo che potrebbero avere problemi di prestazioni se sta ancora disegnando quell'animazione da qualche parte.


Aiuta, grazie. Il valore della durata non ha importanza
iscariota

soluzione più pulita finora
Liran Cohen

16

Sto postando la mia soluzione per chiarezza. La soluzione è abbastanza semplice. Se stai tentando di imitare l'animazione della transazione del frammento genitore, aggiungi semplicemente un'animazione personalizzata alla transazione del frammento figlio con la stessa durata. Oh, e assicurati di impostare l'animazione personalizzata prima di add ().

getChildFragmentManager().beginTransaction()
        .setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none)
        .add(R.id.container, nestedFragment)
        .commit();

L'xml per R.anim.none (il tempo di entrata / uscita dell'animazione dei miei genitori è di 250 ms)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate android:fromXDelta="0" android:toXDelta="0" android:duration="250" />
</set>

Ho fatto qualcosa di molto simile ma ho usato "mostra" anziché "aggiungi" durante l'aggiornamento del bambino. Ho anche aggiunto "getChildFragmentManager (). ExecutePendingTransactions ()", anche se non sono sicuro che sia strettamente necessario. Questa soluzione funziona bene, però, e non richiede di "fornire un'immagine" del frammento come alcuni suggeriscono.
Brian Yencho

È stato fantastico. Tuttavia, ho riscontrato un ritardo durante il cambio dei frammenti di mio figlio. Per evitare questo, è sufficiente impostare il 2 ° parametro senza anim:fragmentTransaction.setCustomAnimations(R.anim.none, 0, R.anim.none, R.anim.none)
ono

7

Capisco che questo potrebbe non essere in grado di risolvere completamente il tuo problema, ma forse soddisferà le esigenze di qualcun altro, puoi aggiungere enter/ exite popEnter/ popExitanimazioni ai tuoi figli Fragmentche in realtà non muovono / animano Fragmenti messaggi. Finché le animazioni hanno la stessa durata / offset delle Fragmentanimazioni principali , sembreranno spostarsi / animarsi con l'animazione del genitore.


1
Ho assegnato la taglia a Luksprog poiché la sua soluzione funziona universalmente. Ho provato il trucco delle animazioni statiche (la durata in realtà non ha importanza - una volta che il genitore è andato, le visualizzazioni ovviamente scompaiono) ma non hanno funzionato in tutti i casi possibili (vedi i miei commenti sotto la domanda). Inoltre, questo approccio lascia trapelare l'astrazione, poiché il genitore di un frammento con bambini deve conoscere questo fatto e adottare misure aggiuntive per impostare le animazioni dei bambini. In ogni caso, grazie mille per il tuo tempo!
Delyan

D'accordo, questa è più una soluzione alternativa che una soluzione a tenuta stagna e può essere considerata leggermente fragile. Ma funzionerà per casi semplici.
Steven Byle

4

puoi farlo nel frammento figlio.

@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
    if (true) {//condition
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(getView(), "alpha", 1, 1);
        objectAnimator.setDuration(333);//time same with parent fragment's animation
        return objectAnimator;
    }
    return super.onCreateAnimator(transit, enter, nextAnim);
}

Grazie! Forse non la soluzione migliore, ma forse la più semplice.
bug56

2

@@@@@@@@@@@@@@@@@@@@@@@@@@@

EDIT: ho finito per non implementare questa soluzione perché c'erano altri problemi che questo ha. Square ha recentemente pubblicato 2 librerie che sostituiscono i frammenti. Direi che questa potrebbe effettivamente essere un'alternativa migliore rispetto al tentativo di hackerare i frammenti per fare qualcosa che Google non vuole che facciano.

http://corner.squareup.com/2014/01/mortar-and-flow.html

@@@@@@@@@@@@@@@@@@@@@@@@@@@

Ho pensato che avrei messo a punto questa soluzione per aiutare le persone che hanno questo problema in futuro. Se segui la conversazione dei poster originali con altre persone e guardi il codice che ha pubblicato, vedrai che il poster originale alla fine giunge alla conclusione di utilizzare un'animazione non operativa sui frammenti figlio mentre anima il frammento genitore. Questa soluzione non è l'ideale in quanto ti obbliga a tenere traccia di tutti i frammenti figlio, che possono essere complicati quando si utilizza un ViewPager con FragmentPagerAdapter.

Dato che uso Child Fragments dappertutto, ho escogitato questa soluzione che è efficiente e modulare (quindi può essere facilmente rimossa) nel caso in cui lo aggiustassero e questa animazione non operativa non fosse più necessaria.

Ci sono molti modi per implementarlo. Ho scelto di utilizzare un singleton e lo chiamo ChildFragmentAnimationManager. Fondamentalmente terrà traccia di un frammento di bambino per me in base al suo genitore e applicherà un'animazione no-op ai bambini quando richiesto.

public class ChildFragmentAnimationManager {

private static ChildFragmentAnimationManager instance = null;

private Map<Fragment, List<Fragment>> fragmentMap;

private ChildFragmentAnimationManager() {
    fragmentMap = new HashMap<Fragment, List<Fragment>>();
}

public static ChildFragmentAnimationManager instance() {
    if (instance == null) {
        instance = new ChildFragmentAnimationManager();
    }
    return instance;
}

public FragmentTransaction animate(FragmentTransaction ft, Fragment parent) {
    List<Fragment> children = getChildren(parent);

    ft.setCustomAnimations(R.anim.no_anim, R.anim.no_anim, R.anim.no_anim, R.anim.no_anim);
    for (Fragment child : children) {
        ft.remove(child);
    }

    return ft;
}

public void putChild(Fragment parent, Fragment child) {
    List<Fragment> children = getChildren(parent);
    children.add(child);
}

public void removeChild(Fragment parent, Fragment child) {
    List<Fragment> children = getChildren(parent);
    children.remove(child);
}

private List<Fragment> getChildren(Fragment parent) {
    List<Fragment> children;

    if ( fragmentMap.containsKey(parent) ) {
        children = fragmentMap.get(parent);
    } else {
        children = new ArrayList<Fragment>(3);
        fragmentMap.put(parent, children);
    }

    return children;
}

}

Successivamente è necessario disporre di una classe che estenda Fragment estendendo tutti i tuoi Fragments (almeno i tuoi Child Fragments). Ho già avuto questa classe e la chiamo BaseFragment. Quando viene creata una vista frammenti, la aggiungiamo a ChildFragmentAnimationManager e la rimuoviamo quando viene distrutta. Puoi farlo su Attacco / Scollega o altri metodi di corrispondenza nella sequenza. La mia logica per scegliere Crea / Distruggi vista era perché se un frammento non ha una vista, non mi interessa animarlo per continuare a essere visto. Questo approccio dovrebbe funzionare meglio anche con ViewPagers che usano Fragments in quanto non terrai traccia di ogni singolo Fragment che contiene un FragmentPagerAdapter, ma piuttosto solo 3.

public abstract class BaseFragment extends Fragment {

@Override
public  View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    Fragment parent = getParentFragment();
    if (parent != null) {
        ChildFragmentAnimationManager.instance().putChild(parent, this);
    }

    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onDestroyView() {
    Fragment parent = getParentFragment();
    if (parent != null) {
        ChildFragmentAnimationManager.instance().removeChild(parent, this);
    }

    super.onDestroyView();
}

}

Ora che tutti i tuoi frammenti sono archiviati in memoria dal frammento genitore, puoi chiamare animate su di essi in questo modo ei frammenti di tuo figlio non scompariranno.

FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ChildFragmentAnimationManager.instance().animate(ft, ReaderFragment.this)
                    .setCustomAnimations(R.anim.up_in, R.anim.up_out, R.anim.down_in, R.anim.down_out)
                    .replace(R.id.container, f)
                    .addToBackStack(null)
                    .commit();

Inoltre, solo così ce l'hai, ecco il file no_anim.xml che va nella tua cartella res / anim:

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

Ancora una volta, non penso che questa soluzione sia perfetta, ma è molto meglio che per ogni istanza hai un frammento figlio, che implementa codice personalizzato nel frammento genitore per tenere traccia di ogni figlio. Ci sono stato e non è divertente.


1

Penso di aver trovato una soluzione migliore a questo problema rispetto all'istantanea del frammento corrente in una bitmap come suggerito da Luksprog.

Il trucco è nascondere il frammento che viene rimosso o scollegato e solo dopo che le animazioni sono state completate il frammento viene rimosso o scollegato nella propria transazione di frammento.

Immagina di avere FragmentAe FragmentB, entrambi con frammenti secondari. Ora quando faresti normalmente:

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .add(R.id.fragmentHolder, new FragmentB())
  .remove(fragmentA)    <-------------------------------------------
  .addToBackStack(null)
  .commit()

Invece lo fai

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .add(R.id.fragmentHolder, new FragmentB())
  .hide(fragmentA)    <---------------------------------------------
  .addToBackStack(null)
  .commit()

fragmentA.removeMe = true;

Ora per l'implementazione del frammento:

public class BaseFragment extends Fragment {

    protected Boolean detachMe = false;
    protected Boolean removeMe = false;

    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        if (nextAnim == 0) {
            if (!enter) {
                onExit();
            }

            return null;
        }

        Animation animation = AnimationUtils.loadAnimation(getActivity(), nextAnim);
        assert animation != null;

        if (!enter) {
            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    onExit();
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                }
            });
        }

        return animation;
    }

    private void onExit() {
        if (!detachMe && !removeMe) {
            return;
        }

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        if (detachMe) {
            fragmentTransaction.detach(this);
            detachMe = false;
        } else if (removeMe) {
            fragmentTransaction.remove(this);
            removeMe = false;
        }
        fragmentTransaction.commit();
    }
}

Il popBackStack non causa un errore perché sta cercando di mostrare un frammento che è stato staccato?
Alexandre

1

Ho riscontrato lo stesso problema con il frammento di mappa. Continuava a scomparire durante l'animazione di uscita del frammento contenente. La soluzione alternativa consiste nell'aggiungere un'animazione per il frammento di mappa figlio che lo manterrà visibile durante l'animazione di uscita del frammento padre. L'animazione del frammento figlio mantiene il suo alfa al 100% durante il suo periodo di durata.

Animazione: res / animator / keep_child_fragment.xml

<?xml version="1.0" encoding="utf-8"?>    
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:propertyName="alpha"
        android:valueFrom="1.0"
        android:valueTo="1.0"
        android:duration="@integer/keep_child_fragment_animation_duration" />
</set>

L'animazione viene quindi applicata quando il frammento di mappa viene aggiunto al frammento padre.

Frammento genitore

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.map_parent_fragment, container, false);

    MapFragment mapFragment =  MapFragment.newInstance();

    getChildFragmentManager().beginTransaction()
            .setCustomAnimations(R.animator.keep_child_fragment, 0, 0, 0)
            .add(R.id.map, mapFragment)
            .commit();

    return view;
}

Infine, la durata dell'animazione del frammento figlio viene impostata in un file di risorse.

valori / interi.xml

<resources>
  <integer name="keep_child_fragment_animation_duration">500</integer>
</resources>

0

Per animare la scomparsa di frammenti neasted possiamo forzare il pop back stack su ChildFragmentManager. Questo attiverà l'animazione della transizione. Per fare ciò dobbiamo recuperare l'evento OnBackButtonPressed o ascoltare le modifiche al backstack.

Ecco un esempio con il codice.

View.OnClickListener() {//this is from custom button but you can listen for back button pressed
            @Override
            public void onClick(View v) {
                getChildFragmentManager().popBackStack();
                //and here we can manage other fragment operations 
            }
        });

  Fragment fr = MyNeastedFragment.newInstance(product);

  getChildFragmentManager()
          .beginTransaction()
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
                .replace(R.neasted_fragment_container, fr)
                .addToBackStack("Neasted Fragment")
                .commit();

0

Recentemente mi sono imbattuto in questo problema nella mia domanda: frammenti nidificati che transitano in modo errato

Ho una soluzione che risolve questo problema senza salvare una bitmap, né utilizzare la riflessione o altri metodi insoddisfacenti.

Un progetto di esempio può essere visualizzato qui: https://github.com/zafrani/NestedFragmentTransitions

Una GIF dell'effetto può essere visualizzata qui: https://imgur.com/94AvrW4

Nel mio esempio ci sono 6 frammenti figli, divisi tra due frammenti genitore. Sono in grado di ottenere le transizioni di entrata, uscita, pop e push senza problemi. Anche le modifiche alla configurazione e le backpress vengono gestite con successo.

La maggior parte della soluzione è nella mia funzione BaseFragment (il frammento esteso dai miei figli e frammenti genitore) onCreateAnimator che assomiglia a questo:

   override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator {
    if (isConfigChange) {
        resetStates()
        return nothingAnim()
    }

    if (parentFragment is ParentFragment) {
        if ((parentFragment as BaseFragment).isPopping) {
            return nothingAnim()
        }
    }

    if (parentFragment != null && parentFragment.isRemoving) {
        return nothingAnim()
    }

    if (enter) {
        if (isPopping) {
            resetStates()
            return pushAnim()
        }
        if (isSuppressing) {
            resetStates()
            return nothingAnim()
        }
        return enterAnim()
    }

    if (isPopping) {
        resetStates()
        return popAnim()
    }

    if (isSuppressing) {
        resetStates()
        return nothingAnim()
    }

    return exitAnim()
}

L'attività e il frammento padre sono responsabili dell'impostazione degli stati di questi valori booleani. È più facile vedere come e dove dal mio progetto di esempio.

Non sto usando frammenti di supporto nel mio esempio, ma la stessa logica può essere utilizzata con loro e con la loro funzione onCreateAnimation


0

Un modo semplice per risolvere questo problema è utilizzare la Fragmentclasse di questa libreria invece della classe dei frammenti di libreria standard:

https://github.com/marksalpeter/contract-fragment

Come nota a margine, il pacchetto contiene anche un utile modello delegato chiamato ContractFragmentche potresti trovare utile per creare le tue app sfruttando la relazione del frammento padre-figlio.


0

Dalla risposta sopra di @kcoppock,

se hai Attività-> Frammento-> Frammenti (impilamento multiplo, il seguente aiuta), una piccola modifica alla migliore risposta IMHO.

public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {

    final Fragment parent = getParentFragment();

    Fragment parentOfParent = null;

    if( parent!=null ) {
        parentOfParent = parent.getParentFragment();
    }

    if( !enter && parent != null && parentOfParent!=null && parentOfParent.isRemoving()){
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else
    if (!enter && parent != null && parent.isRemoving()) {
        // This is a workaround for the bug where child fragments disappear when
        // the parent is removed (as all children are first removed from the parent)
        // See https://code.google.com/p/android/issues/detail?id=55228
        Animation doNothingAnim = new AlphaAnimation(1, 1);
        doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
        return doNothingAnim;
    } else {
        return super.onCreateAnimation(transit, enter, nextAnim);
    }
}

0

Il mio problema era sulla rimozione del frammento genitore (ft.remove (fragment)), le animazioni figlio non stavano accadendo.

Il problema di base è che i frammenti figli vengono immediatamente DISTRUTTI PRIMA che i frammenti dei genitori escano dall'animazione.

Le animazioni personalizzate dei frammenti figlio non vengono eseguite alla rimozione del frammento padre

Come altri sono sfuggiti, nascondere il GENITORE (e non il bambino) prima della rimozione del GENITORE è la strada da percorrere.

            val ft = fragmentManager?.beginTransaction()
            ft?.setCustomAnimations(R.anim.enter_from_right,
                    R.anim.exit_to_right)
            if (parentFragment.isHidden()) {
                ft?.show(vehicleModule)
            } else {
                ft?.hide(vehicleModule)
            }
            ft?.commit()

Se vuoi effettivamente rimuovere il genitore dovresti probabilmente impostare un ascoltatore sulla tua animazione personalizzata per sapere quando l'animazione è terminata, quindi puoi tranquillamente fare un po 'di finalizzazione sul Parent Fragment (rimuovi). Se non lo fai, in modo tempestivo, potresti finire per uccidere l'animazione. NB l'animazione viene eseguita su una propria coda asincrona.

A proposito, non hai bisogno di animazioni personalizzate sul frammento figlio, poiché erediteranno le animazioni padre.



0

Vecchio thread, ma nel caso qualcuno inciampi qui:

Tutti gli approcci di cui sopra sembrano molto poco attraenti per me, la soluzione bitmap è molto sporca e non performante; gli altri richiedono che i frammenti figlio conoscano la durata della transizione utilizzata nella transazione utilizzata per creare il frammento figlio in questione. Una soluzione migliore ai miei occhi è qualcosa di simile alla seguente:

val currentFragment = supportFragmentManager.findFragmentByTag(TAG)
val transaction = supportFragmentManager
    .beginTransaction()
    .setCustomAnimations(anim1, anim2, anim1, anim2)
    .add(R.id.fragmentHolder, FragmentB(), TAG)
if (currentFragment != null) {
    transaction.hide(currentFragment).commit()
    Handler().postDelayed({
        supportFragmentManager.beginTransaction().remove(currentFragment).commit()
    }, DURATION_OF_ANIM)
} else {
    transaction.commit()
}

Nascondiamo semplicemente il frammento corrente e aggiungiamo il nuovo frammento, quando l'animazione è terminata rimuoviamo il vecchio frammento. In questo modo viene gestito in un unico punto e non viene creata alcuna bitmap.

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.