Zagnieżdżone fragmenty znikają podczas animacji przejścia


99

Oto scenariusz: Aktywność zawiera fragment A, który z kolei używa getChildFragmentManager()się do dodawania fragmenty A1i A2w swej onCreatetak:

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

Jak na razie wszystko działa zgodnie z oczekiwaniami.

Następnie uruchamiamy następującą transakcję w Aktywności:

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

Podczas przejścia enteranimacje dla fragmentów Bdziałają poprawnie, ale fragmenty A1 i A2 całkowicie znikają . Kiedy cofamy transakcję przyciskiem Wstecz, inicjalizują się one poprawnie i wyświetlają się normalnie podczas popEnteranimacji.

W moich krótkich testach zrobiło się dziwniej - jeśli ustawię animacje dla fragmentów podrzędnych (patrz poniżej), exitanimacja działa sporadycznie, gdy dodamy fragmentB

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

Efekt, który chcę osiągnąć jest prosty - chcę, aby animacja na fragmencie (anim2) działała exit(a może powinna być popExit?), AAnimując cały kontener, w tym jego zagnieżdżone elementy podrzędne.

Czy jest jakiś sposób, aby to osiągnąć?

Edit : Proszę znaleźć przypadek testowy tutaj

Edit2 : Dziękuję @StevenByle za popychanie mnie do dalszych prób ze statycznymi animacjami. Najwyraźniej możesz ustawić animacje na zasadzie per-op (nie globalne dla całej transakcji), co oznacza, że ​​dzieci mogą mieć nieokreślony zestaw animacji statycznej, podczas gdy ich rodzic może mieć inną animację i całość może zostać zatwierdzona w jednej transakcji . Zobacz dyskusję poniżej i zaktualizowany projekt przypadku testowego .


Co to jest R.id.fragmentHolderw odniesieniu do A, A1, A2 itd.?
CommonsWare,

fragmentHolder to identyfikator w układzie działania, fragment {jeden, dwa} Holdery znajdują się w układzie fragmentu A. Wszystkie trzy są różne. Fragment A został początkowo dodany do fragmentHolder (tj. Fragment B zastępuje fragment A).
Delyan

Utworzyłem przykładowy projekt tutaj: github.com/BurntBrunch/NestedFragmentsAnimationsTest , w repozytorium znajduje się również apk. To naprawdę irytujący błąd i szukam sposobu, aby go obejść (zakładając, że nie ma go w moim kodzie).
Delyan

Teraz wiem trochę więcej na ten temat. Powodem, dla którego fragmenty znikają, jest to, że dzieci obsługują zdarzenia cyklu życia przed rodzicem. Zasadniczo A1 i A2 są usuwane przed A, a ponieważ nie mają ustawionych animacji, znikają nagle. Sposobem na nieco złagodzenie tego jest jawne usunięcie A1 i A2 w transakcji, która zastępuje A. W ten sposób animują się po wyjściu, jednak ich prędkość animacji jest podniesiona do kwadratu, ponieważ kontener nadrzędny również jest animowany. Docenione byłoby rozwiązanie, które nie wytwarza tego artefaktu.
Delyan

Zmiana (zamiana fragmentu startera), o której wspominasz w pytaniu, jest tą, którą naprawdę chcesz zrobić, czy to tylko przykład? Wywołasz tę changeFragmentmetodę tylko raz?
Luksprog

Odpowiedzi:


37

Aby użytkownik nie widział, jak zagnieżdżone fragmenty znikają, gdy fragment nadrzędny jest usuwany / zastępowany w transakcji, można „zasymulować” te fragmenty, które wciąż są obecne, dostarczając ich obraz, gdy pojawiały się na ekranie. Ten obraz będzie używany jako tło dla kontenera zagnieżdżonych fragmentów, więc nawet jeśli widoki zagnieżdżonego fragmentu znikną, obraz zasymuluje ich obecność. Ponadto nie uważam utraty interaktywności z widokami zagnieżdżonego fragmentu za problem, ponieważ nie sądzę, aby użytkownik chciał, aby użytkownik działał na nich, gdy są właśnie w trakcie usuwania (prawdopodobnie jako działanie użytkownika, ponieważ dobrze).

Zrobiłem mały przykład z ustawieniem obrazu tła (coś podstawowego).


1
Postanowiłem przyznać ci nagrodę, ponieważ to było rozwiązanie, z którego korzystałem. Dziękuje bardzo za Twój czas!
Delyan

17
Wow, takie brudne. Długość, którą my, programiści Androida, musimy przejść tylko dla odrobiny zręczności
Dean Wild

1
Mam Viewpager Z drugiej zakładki zastępuję inny fragment, a kiedy go naciskam, muszę pokazać drugą zakładkę Viewpager, otwiera się, ale pokazuje pustą stronę. Wypróbowałem to, co zasugerowałeś w powyższym wątku, ale nadal jest to samo.
Harish

Problem pozostaje, gdy użytkownik wróci, jak napisał @Harish
Ewoks

1
Wow poważnie? jest 2018 i to wciąż jest coś? :(
Archie G. Quiñones

69

Wydaje się więc, że istnieje wiele różnych obejść tego problemu, ale w oparciu o odpowiedź @ Jayd16 myślę, że znalazłem całkiem solidne rozwiązanie typu catch-all, które nadal pozwala na niestandardowe animacje przejścia na fragmentach podrzędnych i nie wymaga robienia pamięć podręczna mapy bitowej układu.

Miej BaseFragmentklasę, która rozszerza Fragment, i spraw, aby wszystkie twoje fragmenty rozszerzały tę klasę (nie tylko fragmenty potomne).

W tej BaseFragmentklasie dodaj następujące informacje:

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

Niestety wymaga refleksji; Jednak ponieważ to obejście dotyczy biblioteki pomocy technicznej, nie ryzykujesz zmiany podstawowej implementacji, chyba że zaktualizujesz bibliotekę pomocy technicznej. Jeśli tworzysz bibliotekę wsparcia ze źródła, możesz dodać akcesor dla następnego identyfikatora zasobu animacji do Fragment.javai usunąć potrzebę refleksji.

To rozwiązanie eliminuje potrzebę "zgadywania" czasu trwania animacji rodzica (tak, że animacja "nic nie rób" będzie miała taki sam czas trwania jak animacja wyjścia rodzica) i nadal pozwala na tworzenie niestandardowych animacji na fragmentach potomnych (np. Jeśli ponowna zamiana fragmentów podrzędnych z różnymi animacjami).


5
To moje ulubione rozwiązanie w wątku. Nie wymaga bitmapy, nie wymaga żadnego dodatkowego kodu w fragmencie podrzędnym i tak naprawdę nie powoduje wycieku informacji od rodzica do fragmentu podrzędnego.
jacobhyphenated

1
@EugenPechanec Potrzebujesz nextAnim z fragmentu nadrzędnego - a nie tego od dziecka. O to chodzi.
Kevin Coppock

1
Niestety, takie podejście powoduje wyciek pamięci dla fragmentów podrzędnych i niejawnie dla fragmentu nadrzędnego, a także w wersjach Androida poniżej Lollipopa :(
Cosmin

8
Dziękuję za to bardzo przydatne rozwiązanie, jednak wymaga niewielkiej aktualizacji do pracy z obecną biblioteką wsparcia (27.0.2, nie wiem, która wersja zepsuła ten kod). mNextAnimznajduje się teraz wewnątrz mAnimationInfoobiektu. Możesz uzyskać do niego dostęp w następujący sposób:Field animInfoField = Fragment.class.getDeclaredField("mAnimationInfo"); animInfoField.setAccessible(true); Object animationInfo = animInfoField.get(fragment); Field nextAnimField = animationInfo.getClass().getDeclaredField("mNextAnim");
David Lericolais

5
@DavidLericolais, chciałbym dodać kolejną linię kodu po Twoim. val nextAnimResource = nextAnimField.getInt(animationInfo);aby zastąpić linięint nextAnimResource = nextAnimField.getInt(fragment);
tingyik90

32

Udało mi się wymyślić całkiem czyste rozwiązanie. IMO jest najmniej hacky i chociaż technicznie jest to rozwiązanie „rysuj bitmapę”, przynajmniej jest to abstrakcyjne przez fragment lib.

Upewnij się, że fragi Twojego dziecka zastępują klasę nadrzędną tym:

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

Jeśli mamy animację wyjścia na fragmencie dzieci, będą one animowane zamiast mrugać. Możemy to wykorzystać, mając animację, która po prostu rysuje fragmenty potomne w pełnej alfa przez pewien czas. W ten sposób pozostaną widoczne we fragmencie nadrzędnym podczas jego animacji, zapewniając pożądane zachowanie.

Jedyny problem, jaki przychodzi mi do głowy, to śledzenie tego czasu trwania. Może mógłbym ustawić to na dużą liczbę, ale obawiam się, że może to mieć problemy z wydajnością, jeśli nadal gdzieś rysuje tę animację.


To pomaga, dzięki. Wartość czasu trwania nie ma znaczenia
iscariot

jak dotąd najczystsze rozwiązanie
Liran Cohen

16

Dla jasności zamieszczam moje rozwiązanie. Rozwiązanie jest dość proste. Jeśli próbujesz naśladować animację transakcji fragmentu rodzica, po prostu dodaj niestandardową animację do transakcji fragmentu potomnego o tym samym czasie trwania. Aha i upewnij się, że ustawiłeś niestandardową animację przed add ().

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

XML dla R.anim.none (czas animacji wejścia / wyjścia moich rodziców wynosi 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>

Zrobiłem coś bardzo podobnego, ale podczas aktualizacji dziecka użyłem „pokaż” zamiast „dodaj”. Dodałem również "getChildFragmentManager (). ExecutePendingTransactions ()", chociaż nie jestem pewien, czy jest to absolutnie konieczne. To rozwiązanie działa jednak dobrze i nie wymaga „dostarczania obrazu” fragmentu, jak niektórzy sugerują.
Brian Yencho

To było świetne. Jednak dostałem opóźnienie podczas przełączania fragmentów mojego dziecka. aby tego uniknąć, po prostu ustaw drugi parametr bez anim:fragmentTransaction.setCustomAnimations(R.anim.none, 0, R.anim.none, R.anim.none)
ono

7

Rozumiem, że to może nie być w stanie całkowicie rozwiązać twojego problemu, ale może będzie to odpowiadało potrzebom kogoś innego, możesz dodać enter/ exiti popEnter/ popExitanimacje do swoich dzieci Fragment, które tak naprawdę nie poruszają / animują Fragment. Dopóki animacje mają taki sam czas trwania / przesunięcie jak ich Fragmentanimacje nadrzędne , będą wyglądać na poruszające / animowane wraz z animacją rodzica.


1
Przyznałem nagrodę Luksprogowi, ponieważ jego rozwiązanie działa uniwersalnie. Wypróbowałem sztuczkę z animacjami statycznymi (czas trwania właściwie nie ma znaczenia - po wyjściu rodzica widoki oczywiście znikają), ale nie działały we wszystkich możliwych przypadkach (zobacz moje komentarze pod pytaniem). Takie podejście przecieka również abstrakcję, ponieważ rodzic fragmentu z dziećmi musi o tym wiedzieć i podjąć dodatkowe kroki, aby ustawić animacje dla dzieci. W każdym razie bardzo dziękuję za poświęcony czas!
Delyan

Zgoda, jest to raczej obejście niż szczelne rozwiązanie i można je uznać za nieco delikatne. Ale zadziała w prostych przypadkach.
Steven Byle

4

możesz to zrobić we fragmencie podrzędnym.

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

Dziękuję Ci! Może nie najlepsze, ale prawdopodobnie najprostsze rozwiązanie.
bug56

2

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

EDYCJA: Skończyło się na tym, że nie zaimplementowałem tego rozwiązania, ponieważ były inne problemy, które ma. Niedawno wyszedł Square z 2 bibliotekami, które zastępują fragmenty. Powiedziałbym, że może to być lepsza alternatywa niż próba włamania fragmentów do zrobienia czegoś, czego Google nie chce, aby robili.

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

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

Pomyślałem, że zaproponuję to rozwiązanie, aby pomóc ludziom, którzy mają ten problem w przyszłości. Jeśli prześledzisz oryginalną rozmowę plakatów z innymi osobami i spojrzysz na opublikowany przez niego kod, zobaczysz, że oryginalny plakat ostatecznie kończy się użyciem animacji bez operacji na fragmentach podrzędnych podczas animowania fragmentu nadrzędnego. To rozwiązanie nie jest idealne, ponieważ zmusza Cię do śledzenia wszystkich fragmentów podrzędnych, co może być uciążliwe podczas korzystania z ViewPager z FragmentPagerAdapter.

Ponieważ wszędzie używam Child Fragments, wymyśliłem to rozwiązanie, które jest wydajne i modułowe (dzięki czemu można je łatwo usunąć) na wypadek, gdyby kiedykolwiek to naprawili, a ta animacja bez operacji nie jest już potrzebna.

Można to wdrożyć na wiele sposobów. Zdecydowałem się użyć singletona i nazywam go ChildFragmentAnimationManager. Zasadniczo będzie śledzić dla mnie fragment podrzędny na podstawie jego rodzica i zastosuje animację bez operacji do dzieci, gdy zostanie o to poproszony.

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

}

Następnie musisz mieć klasę, która rozszerza Fragment, który rozszerza wszystkie twoje fragmenty (przynajmniej twoje fragmenty potomne). Miałem już tę klasę i nazywam ją BaseFragment. Po utworzeniu widoku fragmentów dodajemy go do ChildFragmentAnimationManager i usuwamy go po zniszczeniu. Możesz to zrobić na Attach / Detach lub w innych dopasowanych metodach w sekwencji. Moja logika wyboru widoku Utwórz / zniszcz widok była taka, że ​​jeśli fragment nie ma widoku, nie obchodzi mnie animowanie go, aby nadal był widoczny. To podejście powinno również działać lepiej z ViewPagerami, które używają fragmentów, ponieważ nie będziesz śledzić każdego pojedynczego fragmentu, który przechowuje FragmentPagerAdapter, ale tylko 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();
}

}

Teraz, gdy wszystkie twoje fragmenty są przechowywane w pamięci przez fragment nadrzędny, możesz wywołać na nich animate w ten sposób, a twoje fragmenty podrzędne nie znikną.

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

Poza tym, tak to masz, oto plik no_anim.xml, który znajduje się w folderze 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>

Ponownie, nie sądzę, aby to rozwiązanie było idealne, ale jest znacznie lepsze niż w przypadku każdego wystąpienia fragmentu potomnego, implementującego niestandardowy kod w fragmencie nadrzędnym, aby śledzić każde dziecko. Byłem tam i to nie jest zabawne.


1

Myślę, że znalazłem lepsze rozwiązanie tego problemu niż zrobienie migawki bieżącego fragmentu do mapy bitowej, jak sugerował Luksprog.

Sztuczka polega na tym, żeby się ukryć usuwanego lub odłączanego fragmentu i dopiero po zakończeniu animacji fragment jest usuwany lub odłączany we własnej transakcji fragmentowej.

Wyobraź sobie, że mamy FragmentAi FragmentB, oba z sub-fragmentami. Teraz, kiedy normalnie byś to zrobił:

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

Zamiast tego robisz

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

fragmentA.removeMe = true;

Teraz do wdrożenia fragmentu:

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

Czy popBackStack nie powoduje błędu, ponieważ próbuje pokazać odłączony fragment?
Alexandre

1

Miałem ten sam problem z fragmentem mapy. Znikał podczas animacji wyjścia zawierającego go fragmentu. Sposób obejścia problemu polega na dodaniu animacji dla fragmentu mapy podrzędnej, która będzie widoczna podczas animacji wyjścia z fragmentu nadrzędnego. Animacja fragmentu podrzędnego zachowuje jego alfa na 100% przez cały czas trwania.

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

Animacja jest następnie stosowana, gdy fragment mapy jest dodawany do fragmentu nadrzędnego.

Fragment nadrzędny

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

Na koniec czas trwania animacji fragmentu potomnego jest ustawiany w pliku zasobów.

wartości / integers.xml

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

0

Aby ożywić znikanie usuniętych fragmentów, możemy wymusić stos pop back na ChildFragmentManager. Spowoduje to uruchomienie animacji przejścia. Aby to zrobić, musimy nadrobić zaległości w zdarzeniu OnBackButtonPressed lub nasłuchiwać zmian wstecz.

Oto przykład z kodem.

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

Niedawno napotkałem ten problem w moim pytaniu: Zagnieżdżone fragmenty przechodzą nieprawidłowo

Mam rozwiązanie, które rozwiązuje ten problem bez zapisywania bitmapy, stosowania odbić lub innych niezadowalających metod.

Przykładowy projekt można obejrzeć tutaj: https://github.com/zafrani/NestedFragmentTransitions

Efekt GIF można obejrzeć tutaj: https://imgur.com/94AvrW4

W moim przykładzie jest 6 fragmentów potomnych podzielonych na dwa fragmenty rodzicielskie. Jestem w stanie osiągnąć przejścia dla wejścia, wyjścia, pop i pchania bez żadnych problemów. Również zmiany konfiguracji i prasy wsteczne są z powodzeniem obsługiwane.

Większość rozwiązania znajduje się w mojej funkcji BaseFragment (fragment rozszerzony przez moje elementy podrzędne i nadrzędne) onCreateAnimator, która wygląda następująco:

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

Aktywność i fragment macierzysty są odpowiedzialne za ustawienie stanów tych wartości logicznych. Łatwiej jest zobaczyć, jak i gdzie z mojego przykładowego projektu.

W moim przykładzie nie używam fragmentów wsparcia, ale ta sama logika może być używana z nimi i ich funkcją onCreateAnimation


0

Prostym sposobem rozwiązania tego problemu jest użycie Fragmentklasy z tej biblioteki zamiast standardowej klasy fragmentu biblioteki:

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

Na marginesie, pakiet zawiera również przydatny wzorzec delegata o nazwie ContractFragment, który może być przydatny do tworzenia aplikacji wykorzystujących relację fragment nadrzędny-podrzędny.


0

Z powyższej odpowiedzi @kcoppock,

jeśli masz Aktywność-> Fragment-> Fragmenty (wielokrotne układanie, poniższe pomaga), drobna edycja najlepszej odpowiedzi 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

Mój problem polegał na usunięciu fragmentu nadrzędnego (ft.remove (fragment)), animacje potomne nie działały.

Podstawowym problemem jest to, że fragmenty potomne są natychmiast ZNISZCZONE PRZED wyjściem z animacji fragmentu rodziców.

Niestandardowe animacje fragmentów podrzędnych nie są wykonywane po usunięciu fragmentu nadrzędnego

Jak inni uniknęli, ukrycie RODZICA (a nie dziecka) przed usunięciem RODZICA jest drogą do zrobienia.

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

Jeśli faktycznie chcesz usunąć rodzica, prawdopodobnie powinieneś ustawić nasłuchiwanie na swojej niestandardowej animacji, aby wiedzieć, kiedy animacja się zakończy, więc możesz bezpiecznie przeprowadzić finalizację na fragmencie nadrzędnym (usuń). Jeśli tego nie zrobisz, w odpowiednim czasie, możesz skończyć z zabiciem animacji. Animacja NB jest wykonywana na własnej asynchronicznej kolejce.

BTW nie potrzebujesz niestandardowych animacji na fragmencie podrzędnym, ponieważ odziedziczą one animacje nadrzędne.



0

Stary wątek, ale na wypadek, gdyby ktoś się tu potknął:

Wszystkie powyższe podejścia są dla mnie bardzo nieatrakcyjne, rozwiązanie bitmapowe jest bardzo brudne i nie działa; inne wymagają, aby fragmenty potomne wiedziały o czasie trwania przejścia używanego w transakcji używanej do utworzenia danego fragmentu podrzędnego. Lepszym rozwiązaniem w moich oczach jest coś takiego:

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

Po prostu ukrywamy aktualny fragment i dodajemy nowy fragment, po zakończeniu animacji usuwamy stary fragment. W ten sposób jest obsługiwany w jednym miejscu i nie jest tworzona żadna bitmapa.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.