Verschachtelte Fragmente verschwinden während der Übergangsanimation

100

Hier ist das Szenario: Aktivität enthält Fragment A, das wiederum Verwendungen getChildFragmentManager()Fragmente hinzuzufügen A1und A2in seinem onCreateetwa so:

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

So weit, so gut, alles läuft wie erwartet.

Wir führen dann die folgende Transaktion in der Aktivität aus:

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

Während des Übergangs werden die enterAnimationen für das Fragment Bkorrekt ausgeführt, die Fragmente A1 und A2 verschwinden jedoch vollständig . Wenn wir die Transaktion mit der Schaltfläche Zurück zurücksetzen, werden sie ordnungsgemäß initialisiert und während der popEnterAnimation normal angezeigt .

In meinen kurzen Tests wurde es seltsamer - wenn ich die Animationen für die untergeordneten Fragmente einstelle (siehe unten), wird die exitAnimation zeitweise ausgeführt, wenn wir Fragmente hinzufügenB

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

Der Effekt, den ich erzielen möchte, ist einfach: Ich möchte, dass die Animation exit(oder sollte es sein popExit?) Auf Fragment A(anim2) ausgeführt wird und den gesamten Container einschließlich seiner verschachtelten untergeordneten Elemente animiert.

Gibt es eine Möglichkeit, dies zu erreichen?

Bearbeiten : Hier finden Sie einen Testfall hier

Edit2 : Vielen Dank an @StevenByle, der mich dazu gedrängt hat, es weiter mit den statischen Animationen zu versuchen. Anscheinend können Sie Animationen pro Operation festlegen (nicht global für die gesamte Transaktion), was bedeutet, dass die Kinder einen unbestimmten statischen Animationssatz haben können, während ihre Eltern eine andere Animation haben können und das Ganze in einer Transaktion festgeschrieben werden kann . Siehe die folgende Diskussion und das aktualisierte Testfallprojekt .

Delyan
quelle
Was ist R.id.fragmentHolderin Bezug auf A, A1, A2 usw.?
CommonsWare
fragmentHolder ist eine ID im Layout der Aktivität, fragment {One, Two} Holder befinden sich im Layout von Fragment A. Alle drei sind unterschiedlich. Fragment A wurde ursprünglich in fragmentHolder hinzugefügt (dh Fragment B ersetzt Fragment A).
Delyan
Ich habe hier ein Beispielprojekt erstellt: github.com/BurntBrunch/NestedFragmentsAnimationsTest , es gibt auch eine apk im Repository. Dies ist ein wirklich ärgerlicher Fehler und ich suche nach einer Möglichkeit, ihn zu umgehen (vorausgesetzt, er ist nicht in meinem Code enthalten).
Delyan
Ich weiß jetzt etwas mehr über dieses Problem. Der Grund, warum die Fragmente verschwinden, liegt darin, dass die Kinder die Lebenszyklusereignisse vor dem Elternteil behandeln. Im Wesentlichen werden A1 und A2 vor A entfernt, und da keine Animationen festgelegt sind, verschwinden sie abrupt. Eine Möglichkeit, dies etwas zu mildern, besteht darin, A1 und A2 in der Transaktion, die A ersetzt, explizit zu entfernen. Auf diese Weise werden sie beim Beenden animiert, ihre Animationsgeschwindigkeit wird jedoch quadriert, da der übergeordnete Container ebenfalls animiert. Eine Lösung, die dieses Artefakt nicht erzeugt, wäre willkommen.
Delyan
Die Änderung (die das Starterfragment ersetzt), die Sie in der Frage erwähnen, ist die tatsächliche, die Sie vornehmen möchten, oder es ist nur ein Beispiel? Sie werden die changeFragmentMethode nur einmal aufrufen ?
Luksprog

Antworten:

36

Um zu vermeiden, dass der Benutzer sieht, dass die verschachtelten Fragmente verschwinden, wenn das übergeordnete Fragment in einer Transaktion entfernt / ersetzt wird, können Sie die noch vorhandenen Fragmente "simulieren", indem Sie ein Bild von ihnen bereitstellen, wie sie auf dem Bildschirm angezeigt werden. Dieses Bild wird als Hintergrund für den Container für verschachtelte Fragmente verwendet. Selbst wenn die Ansichten des verschachtelten Fragments verschwinden, simuliert das Bild deren Vorhandensein. Außerdem sehe ich es nicht als Problem an, die Interaktivität mit den Ansichten des verschachtelten Fragments zu verlieren, da ich nicht glaube, dass der Benutzer auf sie reagieren soll, wenn sie gerade entfernt werden (wahrscheinlich als Benutzeraktion als Gut).

Ich habe ein kleines Beispiel mit dem Einrichten des Hintergrundbilds gemacht (etwas Grundlegendes).

Luksprog
quelle
1
Ich habe beschlossen, Ihnen das Kopfgeld zu gewähren, da dies die Lösung war, die ich letztendlich verwendet habe. Vielen Dank für Ihre Zeit!
Delyan
16
Wow, so dreckig. Die Längen, zu denen wir Android-Entwickler gehen müssen, um ein bisschen Slickness zu erreichen
Dean Wild
1
Ich habe einen Viewpager von der zweiten Registerkarte Ich ersetze ein anderes Fragment und wenn ich darauf drücke, muss ich die zweite Registerkarte des Viewpagers anzeigen, sie wird geöffnet, aber es wird eine leere Seite angezeigt. Ich habe versucht, was Sie im obigen Thread vorgeschlagen haben, aber es ist immer noch dasselbe.
Harish
Das Problem bleibt bestehen, wenn der Benutzer zurückkommt, wie @Harish schrieb
Ewoks
1
Wow im Ernst? es ist 2018 und das ist immer noch eine Sache? :(
Archie G. Quiñones
69

Es scheint also viele verschiedene Problemumgehungen zu geben, aber basierend auf der Antwort von @ Jayd16 denke ich, dass ich eine ziemlich solide Gesamtlösung gefunden habe, die immer noch benutzerdefinierte Übergangsanimationen für untergeordnete Fragmente ermöglicht und dies nicht erfordert ein Bitmap-Cache des Layouts.

Haben Sie eine BaseFragmentKlasse, die erweitert wird Fragment, und lassen Sie alle Ihre Fragmente diese Klasse erweitern (nicht nur untergeordnete Fragmente).

Fügen Sie in dieser BaseFragmentKlasse Folgendes hinzu:

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

Es erfordert leider Reflexion; Da diese Problemumgehung jedoch für die Support-Bibliothek gilt, besteht nicht das Risiko, dass sich die zugrunde liegende Implementierung ändert, es sei denn, Sie aktualisieren Ihre Support-Bibliothek. Wenn Sie die Support-Bibliothek aus dem Quellcode erstellen, können Sie einen Accessor für die nächste Animationsressourcen-ID hinzufügen Fragment.javaund die Notwendigkeit der Reflexion beseitigen.

Diese Lösung beseitigt die Notwendigkeit, die Animationsdauer des Elternteils zu "erraten" (so dass die Animation "Nichts tun" dieselbe Dauer hat wie die Exit-Animation des Elternteils), und ermöglicht es Ihnen, weiterhin benutzerdefinierte Animationen für untergeordnete Fragmente zu erstellen (z. Kinderfragmente mit verschiedenen Animationen austauschen).

Kevin Coppock
quelle
5
Dies ist meine Lieblingslösung im Thread. Erfordert keine Bitmap, erfordert keinen zusätzlichen Code im untergeordneten Fragment und gibt keine Informationen vom übergeordneten Fragment an das untergeordnete Fragment weiter.
jacobhyphenated
1
@EugenPechanec Sie benötigen das nextAnim aus dem übergeordneten Fragment - nicht das aus dem untergeordneten . Das ist der springende Punkt.
Kevin Coppock
1
Leider verursacht dieser Ansatz einen Speicherverlust für die untergeordneten Fragmente und implizit für das übergeordnete Fragment auch für Android-Versionen unter Lollipop :(
Cosmin
8
Vielen Dank für diese sehr nützliche Lösung. Für die Arbeit mit der aktuellen Support-Bibliothek ist jedoch ein kleines Update erforderlich (27.0.2, ich weiß nicht, welche Version diesen Code beschädigt hat). mNextAnimist jetzt in einem mAnimationInfoObjekt. Sie können wie Field animInfoField = Fragment.class.getDeclaredField("mAnimationInfo"); animInfoField.setAccessible(true); Object animationInfo = animInfoField.get(fragment); Field nextAnimField = animationInfo.getClass().getDeclaredField("mNextAnim");
folgt darauf
5
@DavidLericolais, möchte nach Ihrer weitere Codezeile hinzufügen. val nextAnimResource = nextAnimField.getInt(animationInfo);um die Linie zu ersetzenint nextAnimResource = nextAnimField.getInt(fragment);
tingyik90
32

Ich konnte eine ziemlich saubere Lösung finden. IMO ist das am wenigsten hackige, und obwohl dies technisch die "Draw a Bitmap" -Lösung ist, wird es zumindest durch das Fragment lib abstrahiert.

Stellen Sie sicher, dass Ihre Kinder-Frags eine Elternklasse folgendermaßen überschreiben:

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

Wenn die untergeordneten Frags eine Exit-Animation haben, werden sie animiert, anstatt wegzublinken. Wir können dies ausnutzen, indem wir eine Animation haben, die einfach die untergeordneten Fragmente für eine Dauer mit vollem Alpha zeichnet. Auf diese Weise bleiben sie während der Animation im übergeordneten Fragment sichtbar und geben das gewünschte Verhalten an.

Das einzige Problem, an das ich denken kann, ist, diese Dauer im Auge zu behalten. Ich könnte es vielleicht auf eine große Zahl einstellen, aber ich befürchte, dass es Leistungsprobleme geben könnte, wenn diese Animation noch irgendwo gezeichnet wird.

Jayd16
quelle
Es hilft, danke. Der Wert der Dauer spielt keine Rolle
Ischariot
sauberste Lösung bisher
Liran Cohen
16

Ich poste meine Lösung aus Gründen der Klarheit. Die Lösung ist ganz einfach. Wenn Sie versuchen, die Fragmenttransaktionsanimation des übergeordneten Elements nachzuahmen, fügen Sie der untergeordneten Fragmenttransaktion einfach eine benutzerdefinierte Animation mit derselben Dauer hinzu. Oh und stellen Sie sicher, dass Sie die benutzerdefinierte Animation festlegen, bevor Sie add ().

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

Die XML-Datei für R.anim.none (Die Animationszeit meiner Eltern für das Ein- und Aussteigen beträgt 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>
Maurycy
quelle
Ich habe etwas sehr Ähnliches gemacht, aber beim Aktualisieren des Kindes "show" anstelle von "add" verwendet. Ich habe auch "getChildFragmentManager (). ExecutePendingTransactions ()" hinzugefügt, obwohl ich nicht sicher bin, ob dies unbedingt erforderlich ist. Diese Lösung funktioniert jedoch einwandfrei und erfordert nicht das "Bereitstellen eines Bildes" des Fragments, wie einige vorschlagen.
Brian Yencho
Das war großartig. Ich bekam jedoch die Verzögerung beim Wechseln meiner Kinderfragmente. Um dies zu vermeiden, stellen Sie einfach den zweiten Parameter ohne anim:fragmentTransaction.setCustomAnimations(R.anim.none, 0, R.anim.none, R.anim.none)
Ono
7

Ich verstehe, dass dies möglicherweise nicht in der Lage ist, Ihr Problem vollständig zu lösen, aber vielleicht wird es den Bedürfnissen eines anderen entsprechen. Sie können Ihren Kindern enter/ exitund popEnter/ popExitAnimationen hinzufügen , die die Fragments nicht wirklich bewegen / animieren Fragment. Solange die Animationen dieselbe Dauer / denselben Versatz wie ihre übergeordneten FragmentAnimationen haben, scheinen sie sich mit der Animation der übergeordneten Animation zu bewegen / zu animieren.

Steven Byle
quelle
1
Ich habe Luksprog das Kopfgeld verliehen, da seine Lösung universell funktioniert. Ich habe den Trick mit statischen Animationen ausprobiert (die Dauer spielt eigentlich keine Rolle - sobald die Eltern weg sind, verschwinden die Ansichten offensichtlich), aber sie haben nicht in allen möglichen Fällen funktioniert (siehe meine Kommentare unter der Frage). Bei diesem Ansatz tritt auch eine Abstraktion auf, da die Eltern eines Fragments mit Kindern über diese Tatsache Bescheid wissen und zusätzliche Schritte unternehmen müssen, um die Animationen der Kinder festzulegen. Auf jeden Fall vielen Dank für Ihre Zeit!
Delyan
Einverstanden ist dies eher eine Problemumgehung als eine wasserdichte Lösung und kann als leicht zerbrechlich angesehen werden. Aber es wird für einfache Fälle funktionieren.
Steven Byle
4

Sie können dies im untergeordneten Fragment tun.

@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);
}
Peng Gao
quelle
Danke dir! Vielleicht nicht die beste, aber möglicherweise die einfachste Lösung.
Bug56
2

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

EDIT: Am Ende habe ich diese Lösung nicht implementiert, da es andere Probleme gab, die dies hat. Square hat kürzlich zwei Bibliotheken herausgebracht, die Fragmente ersetzen. Ich würde sagen, dies könnte tatsächlich eine bessere Alternative sein, als zu versuchen, Fragmente in etwas zu hacken, das Google nicht will.

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

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

Ich dachte, ich würde diese Lösung aufstellen, um Menschen zu helfen, die dieses Problem in Zukunft haben. Wenn Sie die Konversation der Originalposter mit anderen Personen nachverfolgen und sich den von ihm geposteten Code ansehen, werden Sie feststellen, dass das Originalposter schließlich zu dem Schluss kommt, dass beim Animieren des übergeordneten Fragments eine No-Op-Animation für die untergeordneten Fragmente verwendet wird. Diese Lösung ist nicht ideal, da Sie gezwungen sind, alle untergeordneten Fragmente im Auge zu behalten. Dies kann bei Verwendung eines ViewPagers mit FragmentPagerAdapter umständlich sein.

Da ich überall Kinderfragmente verwende, habe ich diese Lösung entwickelt, die effizient und modular ist (so dass sie leicht entfernt werden kann), falls sie jemals repariert wird und diese No-Op-Animation nicht mehr benötigt wird.

Es gibt viele Möglichkeiten, wie Sie dies implementieren können. Ich habe mich für einen Singleton entschieden und nenne ihn ChildFragmentAnimationManager. Grundsätzlich wird ein untergeordnetes Fragment für mich anhand seines übergeordneten Elements verfolgt und auf Anfrage eine No-Op-Animation auf die Kinder angewendet.

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

}

Als nächstes benötigen Sie eine Klasse, die Fragment erweitert, die alle Ihre Fragmente erweitern (zumindest Ihre untergeordneten Fragmente). Ich hatte diese Klasse bereits und nenne sie BaseFragment. Wenn eine Fragmentansicht erstellt wird, fügen wir sie dem ChildFragmentAnimationManager hinzu und entfernen sie, wenn sie zerstört wird. Sie können dies mit Attach / Detach oder anderen Übereinstimmungsmethoden in der Sequenz tun. Meine Logik für die Auswahl "Ansicht erstellen / zerstören" war, dass es mir egal ist, ein Fragment zu animieren, damit es weiterhin angezeigt wird, wenn es keine Ansicht hat. Dieser Ansatz sollte auch mit ViewPagern besser funktionieren, die Fragmente verwenden, da Sie nicht jedes einzelne Fragment verfolgen, das ein FragmentPagerAdapter enthält, sondern nur 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();
}

}

Nachdem alle Ihre Fragmente vom übergeordneten Fragment gespeichert wurden, können Sie sie wie folgt animieren, und Ihre untergeordneten Fragmente verschwinden nicht mehr.

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

Nur damit Sie es haben, finden Sie hier die Datei no_anim.xml, die sich in Ihrem Ordner res / anim befindet:

<?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>

Auch hier denke ich nicht, dass diese Lösung perfekt ist, aber sie ist viel besser als für jede Instanz, in der Sie ein untergeordnetes Fragment haben, und implementiert benutzerdefinierten Code im übergeordneten Fragment, um die Übersicht über jedes untergeordnete Fragment zu behalten. Ich war dort und es macht keinen Spaß.

spierce7
quelle
1

Ich glaube, ich habe eine bessere Lösung für dieses Problem gefunden, als das aktuelle Fragment in einer Bitmap zu fotografieren, wie Luksprog vorgeschlagen hat.

Der Trick besteht darin , das entfernte oder abgetrennte Fragment auszublenden. Erst nach Abschluss der Animationen wird das Fragment in einer eigenen Fragmenttransaktion entfernt oder abgetrennt.

Stellen Sie sich vor, wir haben FragmentAund FragmentBbeide mit Unterfragmenten. Nun, wenn Sie normalerweise tun würden:

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

Stattdessen tust du es

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

fragmentA.removeMe = true;

Nun zur Implementierung des Fragments:

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();
    }
}
Danilo
quelle
Würde der popBackStack nicht einen Fehler verursachen, weil er versucht, ein getrenntes Fragment anzuzeigen?
Alexandre
1

Ich hatte das gleiche Problem mit dem Kartenfragment. Es verschwand während der Exit-Animation seines enthaltenen Fragments. Die Problemumgehung besteht darin, eine Animation für das untergeordnete Kartenfragment hinzuzufügen, die während der Exit-Animation des übergeordneten Fragmentes sichtbar bleibt. Die Animation des untergeordneten Fragments hält sein Alpha während seiner Dauer bei 100%.

Animation: 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>

Die Animation wird dann angewendet, wenn das Kartenfragment dem übergeordneten Fragment hinzugefügt wird.

Übergeordnetes Fragment

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

Schließlich wird die Dauer der untergeordneten Fragmentanimation in einer Ressourcendatei festgelegt.

values ​​/ integers.xml

<resources>
  <integer name="keep_child_fragment_animation_duration">500</integer>
</resources>
Evgenii
quelle
0

Um das Verschwinden verschachtelter Fragmente zu animieren, können wir ChildFragmentManager einen Pop-Back-Stapel erzwingen. Dadurch wird eine Übergangsanimation ausgelöst. Dazu müssen wir das OnBackButtonPressed-Ereignis nachholen oder auf Backstack-Änderungen warten.

Hier ist ein Beispiel mit Code.

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();
Mateusz Biedron
quelle
0

Ich bin kürzlich in meiner Frage auf dieses Problem gestoßen : Verschachtelte Fragmente, die falsch übergehen

Ich habe eine Lösung, die dies löst, ohne eine Bitmap zu speichern oder Reflexionen oder andere unbefriedigende Methoden zu verwenden.

Ein Beispielprojekt kann hier angesehen werden: https://github.com/zafrani/NestedFragmentTransitions

Ein GIF des Effekts kann hier eingesehen werden: https://imgur.com/94AvrW4

In meinem Beispiel gibt es 6 untergeordnete Fragmente, die auf zwei übergeordnete Fragmente aufgeteilt sind. Ich kann die Übergänge für Enter, Exit, Pop und Push problemlos erreichen. Konfigurationsänderungen und Zurückdrücken werden ebenfalls erfolgreich behandelt.

Der Großteil der Lösung befindet sich in der onCreateAnimator-Funktion von BaseFragment (dem von meinen untergeordneten und übergeordneten Fragmenten erweiterten Fragment), die folgendermaßen aussieht:

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

Die Aktivität und das übergeordnete Fragment sind für das Festlegen der Zustände dieser Booleschen Werte verantwortlich. Es ist einfacher zu sehen, wie und wo von meinem Beispielprojekt.

In meinem Beispiel werden keine Unterstützungsfragmente verwendet, aber mit ihnen und ihrer onCreateAnimation-Funktion kann dieselbe Logik verwendet werden

zafrani
quelle
0

Eine einfache Möglichkeit, dieses Problem zu beheben, besteht darin, die FragmentKlasse aus dieser Bibliothek anstelle der Standardbibliotheksfragmentklasse zu verwenden:

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

Nebenbei bemerkt enthält das Paket auch ein nützliches Delegatenmuster, ContractFragmentdas Sie möglicherweise zum Erstellen Ihrer Apps unter Nutzung der Eltern-Kind-Fragmentbeziehung als nützlich erachten.

Mark Salpeter
quelle
0

Aus der obigen Antwort von @kcoppock,

Wenn Sie Aktivität-> Fragment-> Fragmente haben (Mehrfachstapelung, das Folgende hilft), eine kleinere Bearbeitung, um meiner Meinung nach die beste Antwort zu erhalten.

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);
    }
}
alekshandru
quelle
0

Mein Problem war das Entfernen des übergeordneten Fragments (ft.remove (Fragment)), untergeordnete Animationen wurden nicht ausgeführt.

Das Grundproblem besteht darin, dass untergeordnete Fragmente sofort VOR dem Verlassen der Animation durch das übergeordnete Fragment ZERSTÖRT werden.

Benutzerdefinierte Animationen für untergeordnete Fragmente werden beim Entfernen des übergeordneten Fragments nicht ausgeführt

Wie andere sich entzogen haben, ist es der richtige Weg, die Eltern (und nicht das Kind) vor der Entfernung der Eltern zu verstecken.

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

Wenn Sie das übergeordnete Element tatsächlich entfernen möchten, sollten Sie wahrscheinlich einen Listener für Ihre benutzerdefinierte Animation einrichten, um zu wissen, wann die Animation beendet ist, damit Sie das übergeordnete Fragment sicher entfernen können (entfernen). Wenn Sie dies nicht rechtzeitig tun, können Sie die Animation möglicherweise beenden. Hinweis: Die Animation wird in einer eigenen asynchronen Warteschlange ausgeführt.

Übrigens benötigen Sie keine benutzerdefinierten Animationen für das untergeordnete Fragment, da diese die übergeordneten Animationen erben.

Ed Manners
quelle
0

Alter Thread, aber für den Fall, dass hier jemand stolpert:

Alle oben genannten Ansätze fühlen sich für mich sehr unattraktiv an. Die Bitmap-Lösung ist sehr schmutzig und nicht performant. Bei den anderen müssen die untergeordneten Fragmente die Dauer des Übergangs kennen, der in der Transaktion zum Erstellen des betreffenden untergeordneten Fragments verwendet wird. Eine bessere Lösung in meinen Augen ist so etwas wie die folgende:

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

Wir verstecken nur das aktuelle Fragment und fügen das neue Fragment hinzu. Wenn die Animation beendet ist, entfernen wir das alte Fragment. Auf diese Weise wird es an einem Ort behandelt und es wird keine Bitmap erstellt.

Jeppeman
quelle