Menedżer fragmentów

FragmentManager to klasa odpowiedzialna za wykonywanie działań na fragmentach aplikacji, takich jak ich dodanie, usunięcie lub zastąpienie i dodanie do stosu wstecznego.

Możliwe, że nigdy nie będziesz mieć możliwości bezpośredniej interakcji z usługą FragmentManager, jeśli jej używasz z biblioteką Jetpack Navigation, ponieważ współpracuje ona z FragmentManager. Każda aplikacja korzystająca z fragmentów jest jednak używając FragmentManager, dlatego ważne jest, aby wiedzieć, co i jak działa.

Tematy na tej stronie:

  • Jak uzyskać dostęp do: FragmentManager.
  • Rola FragmentManager w odniesieniu do Twoich działań i fragmentów.
  • Jak zarządzać stosem wstecznym w FragmentManager.
  • Jak dostarczyć dane i zależności do fragmentów.

Dostęp do FragmentManagera

Dostęp do FragmentManager możesz uzyskać z poziomu aktywności lub z fragmentu.

FragmentActivity i jego podklas, takich jak AppCompatActivity, dostęp do usługi FragmentManager za pośrednictwem usługi getSupportFragmentManager() .

Fragmenty mogą zawierać jeden lub więcej fragmentów podrzędnych. Wewnątrz fragment, możesz uzyskać odwołanie do elementu FragmentManager, którym zarządza przez elementy podrzędne fragmentu getChildFragmentManager() Jeśli musisz uzyskać dostęp do hosta FragmentManager, możesz użyć getParentFragmentManager()

Oto kilka przykładów, które pozwolą zobaczyć zależności między fragmenty, ich hosty i powiązane instancje FragmentManager z każdą z nich.

2 przykłady układu UI pokazujące zależności między
            fragmenty i ich działania hosta
Rysunek 1. 2 przykłady układu interfejsu przedstawiającego między fragmentami a ich działaniami hosta.

Rys. 1 przedstawia dwa przykłady, z których każdy ma jednego hosta aktywności. aktywność hosta w obu tych przykładach wyświetla nawigację najwyższego poziomu użytkownika jako BottomNavigationView odpowiedzialnym za zamianę fragmentu hosta na inny w aplikacji. Każdy ekran jest zaimplementowany jako osobny fragment.

Fragment hosta w przykładzie 1 zawiera dwa fragmenty podrzędne, które aby podzielić ekran. Fragment hosta w przykładzie 2 zawiera plik pojedynczy fragment podrzędny, który tworzy wyświetlany fragment widokiem przesuwanym.

Przy takiej konfiguracji każdy host może mieć FragmentManager który zarządza jego fragmentami podrzędnymi. Zostało to pokazane w rysunek 2 wraz z mapowaniami właściwości między obiektami supportFragmentManager, parentFragmentManager i childFragmentManager.

z każdym hostem powiązany jest własny element FragmentManager
            który zarządza fragmentami podrzędnymi
Rysunek 2. Każdy host ma swoje własne Powiązano z nim konto FragmentManager i zarządza nim we fragmentach podrzędnych.

Odpowiednia właściwość FragmentManager, do której należy się odwołać, zależy od tego, gdzie witryna wywołania znajduje się w hierarchii fragmentów wraz z menedżerem fragmentów do której próbujesz uzyskać dostęp.

Jeśli masz odniesienie do elementu FragmentManager, możesz go użyć do: i manipulowanie fragmentami wyświetlanymi użytkownikowi.

Fragmenty podrzędne

Ogólnie rzecz biorąc, aplikacja składa się z jednej lub niewielkiej liczby aktywności w projekcie aplikacji, przy czym każde działanie reprezentuje grupę powiązanych ekranów. Działanie może być źródłem informacji o tym miejscu. nawigacja najwyższego poziomu oraz miejsce do zakresu obiektów ViewModel i innych stanów widoku między fragmentami. Fragment reprezentuje pojedyncze miejsce docelowe w .

Aby wyświetlać wiele fragmentów jednocześnie, np. w widoku podzielonym lub panelu, można użyć fragmentów podrzędnych zarządzanych przez fragment docelowy i jego menedżer fragmentów podrzędnych.

Inne przypadki użycia fragmentów podrzędnych:

  • Slajdy ekranu, używanie ViewPager2 we fragmencie nadrzędnym do zarządzania serią elementów podrzędnych widoki fragmentów.
  • Nawigacja podrzędna w obrębie zestawu powiązanych ekranów.
  • Jetpack Navigation używa fragmentów podrzędnych jako poszczególnych miejsc docelowych. An aktywność hostuje jeden element nadrzędny NavHostFragment i wypełnia jego miejsce z różnymi podrzędnymi fragmentami miejsca docelowego w miarę przechodzenia przez użytkowników do aplikacji.

Korzystanie z narzędzia FragmentManager

Element FragmentManager zarządza wstecznym stosem fragmentów. W czasie działania FragmentManager może wykonywać operacje tworzenia stosu wstecznego, takie jak dodawanie i usuwanie fragmentów w odpowiedzi na interakcje użytkownika. Każdy zestaw zmian przedsięwzięcia w formie pojedynczej jednostki zwanej FragmentTransaction Więcej informacji o transakcjach fragmentowych znajdziesz w przewodnika po transakcjach dotyczących fragmentów.

Gdy użytkownik kliknie przycisk Wstecz na swoim urządzeniu lub gdy zadzwonisz FragmentManager.popBackStack() transakcja z najwyższym fragmentem ma miejsce w stosie. Jeśli nie ma więcej fragmentów transakcji na stosie, a jeśli nie używasz fragmentów podrzędnych, fragment Wstecz dymki powiązane z aktywnością. Jeśli używasz fragmentów podrzędnych, zapoznaj się z artykułem specjalne uwagi dotyczące fragmentów podrzędnych i równorzędnych.

Gdy dzwonisz addToBackStack() w transakcji, transakcja może zawierać dowolną liczbę operacji, takich jak dodawanie wielu fragmentów lub zastępowanie fragmentów w wielu kontenery.

Po wysunięciu stosu wstecznego wszystkie te elementy na odwrót jako pojedyncze, niepodzielne działanie. Jeśli jednak przed rozmową popBackStack(), a jeśli nie użyło addToBackStack() w przypadku tej transakcji, te operacje nie odwracaj ich. Dlatego w ramach jednego parametru FragmentTransaction unikaj i prowadzimy między nimi.

Wykonaj transakcję

Aby wyświetlić fragment w kontenerze układu, użyj komponentu FragmentManager aby utworzyć FragmentTransaction. W ramach transakcji możesz następnie wykonaj add() lub replace() na kontenerze.

Prosty FragmentTransaction może na przykład wyglądać tak:

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container)
   setReorderingAllowed(true)
   addToBackStack("name") // Name can be null
}

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null)
    .setReorderingAllowed(true)
    .addToBackStack("name") // Name can be null
    .commit();

W tym przykładzie ExampleFragment zastępuje fragment, który jest obecnie w kontenerze układu wskazywanego przez Identyfikator R.id.fragment_container. Przekazywanie klasy fragmentu do funkcji replace() pozwala funkcji FragmentManager na obsługę instancji za pomocą FragmentFactory Więcej informacji znajdziesz w artykule Podawanie zależności fragmentom. .

setReorderingAllowed(true) optymalizuje zmiany stanu fragmentów objętych transakcją by animacje i przejścia działały prawidłowo. Więcej informacji na temat: nawigowanie z użyciem animacji i przejść, patrz Transakcje z wykorzystaniem fragmentów Poruszaj się po fragmentach za pomocą animacji.

Łączę addToBackStack() zatwierdza transakcję na stosie wstecznym. Użytkownik może później odwrócić kolejność transakcji i przywrócić poprzedni fragment, dotykając przycisku Wstecz Przycisk W przypadku dodania lub usunięcia wielu fragmentów w jednym wszystkie te operacje są cofane, gdy zostanie wystrzelony. Opcjonalna nazwa podana w wywołaniu addToBackStack() daje możesz wrócić do określonej transakcji za pomocą popBackStack()

Jeśli nie wywołujesz funkcji addToBackStack() podczas wykonywania transakcji, która usuwa fragment, a po jego zniszczeniu – usunięty fragment jest zniszczony. transakcja jest zatwierdzona i użytkownik nie może wrócić do niej. Jeśli wywołuje funkcję addToBackStack() przy usuwaniu fragmentu, wówczas fragment ma postać tylko STOPPED i ma wartość RESUMED, gdy użytkownik wróci. Widok została zniszczona. Więcej informacji: Cykl życia fragmentów.

Znajdowanie istniejącego fragmentu

Możesz uzyskać odwołanie do bieżącego fragmentu w kontenerze układu za pomocą funkcji findFragmentById() Użyj funkcji findFragmentById(), aby wyszukać fragment według podanego identyfikatora, gdy jest powiększona z pliku XML lub według identyfikatora kontenera, gdy zostanie dodany w funkcji FragmentTransaction Oto przykład:

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container)
   setReorderingAllowed(true)
   addToBackStack(null)
}
...
val fragment: ExampleFragment =
        supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null)
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .commit();
...
ExampleFragment fragment =
        (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);

Do fragmentu możesz też przypisać unikalny tag i uzyskać odwołanie za pomocą findFragmentByTag() Możesz przypisać tag, korzystając z atrybutu XML android:tag we fragmentach, które są zdefiniowane w układzie lub w parametrze add() albo replace() operacji w FragmentTransaction.

Kotlin

supportFragmentManager.commit {
   replace<ExampleFragment>(R.id.fragment_container, "tag")
   setReorderingAllowed(true)
   addToBackStack(null)
}
...
val fragment: ExampleFragment =
        supportFragmentManager.findFragmentByTag("tag") as ExampleFragment

Java

FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
    .replace(R.id.fragment_container, ExampleFragment.class, null, "tag")
    .setReorderingAllowed(true)
    .addToBackStack(null)
    .commit();
...
ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");

Specjalne uwagi dotyczące fragmentów podrzędnych i równorzędnych

Tylko jeden element typu FragmentManager może kontrolować wsteczny stos fragmentów w dowolnym momencie. Jeśli aplikacja wyświetla wiele fragmentów równorzędnych w ekranu w tym samym czasie, a jeśli aplikacja korzysta z fragmentów podrzędnych, Aplikacja FragmentManager jest przeznaczona do obsługi głównej nawigacji w aplikacji.

Aby zdefiniować główny fragment nawigacji wewnątrz transakcji opartej na fragmencie, Wywołaj funkcję setPrimaryNavigationFragment() dla transakcji, przekazując wystąpienie fragmentu, którego Głównym elementem sterującym jest childFragmentManager.

Omówmy strukturę nawigacji jako serię warstw, z których jako najbardziej zewnętrzną warstwę, pakując od niej każdą warstwę podrzędnych fragmentów. Każda warstwa ma 1 główny fragment nawigacji.

Gdy plecy najbardziej wewnętrzna warstwa kontroluje zachowanie nawigacji. Gdy funkcja w warstwie najbardziej wewnętrznej nie ma więcej transakcji z fragmentami, z których można wrócić, element sterujący wróci do następnej warstwy i będzie się powtarzał do chwili do aktywności.

Jeśli jednocześnie wyświetlane są co najmniej dwa fragmenty, tylko Jeden to główny fragment nawigacji. Ustawianie fragmentu , ponieważ główny fragment nawigacji usuwa oznaczenie z poprzedniego fragment. Na podstawie poprzedniego przykładu, jeśli fragment szczegółów ustawisz jako główny fragment nawigacyjny, oznaczenie fragmentu głównego zostało usunięte.

Obsługa kilku tylnych stosów

W niektórych przypadkach aplikacja może obsługiwać wiele stosów wstecznych. Częstym Przykład: aplikacja używa dolnego paska nawigacyjnego. FragmentManager pozwala obsługujesz kilka tylnych stosów za pomocą funkcji saveBackStack() i restoreBackStack() metod. Te metody umożliwiają przełączanie się między trybami możesz zapisać jeden stos wsteczny i przywrócić inny.

Funkcja saveBackStack() działa podobnie do wywoływania popBackStack() z opcjonalnym name: określona transakcja i wszystkie transakcje po niej na a następnie w wysokiej rozdzielczości. Różnica polega na tym, że saveBackStack() zapisuje stan wszystkich fragmentów w wyskakującym okienku transakcji.

Załóżmy np., że wcześniej do stosu tylnego dodano fragment przez wymagające FragmentTransaction za pomocą addToBackStack(), jak pokazano w następujący przykład:

Kotlin

supportFragmentManager.commit {
  replace<ExampleFragment>(R.id.fragment_container)
  setReorderingAllowed(true)
  addToBackStack("replacement")
}

Java

supportFragmentManager.beginTransaction()
  .replace(R.id.fragment_container, ExampleFragment.class, null)
  // setReorderingAllowed(true) and the optional string argument for
  // addToBackStack() are both required if you want to use saveBackStack()
  .setReorderingAllowed(true)
  .addToBackStack("replacement")
  .commit();

W takim przypadku możesz zapisać transakcję dotyczącą fragmentu oraz stan ExampleFragment, dzwoniąc pod numer saveBackStack():

Kotlin

supportFragmentManager.saveBackStack("replacement")

Java

supportFragmentManager.saveBackStack("replacement");

Możesz wywołać funkcję restoreBackStack() z tym samym parametrem name, aby przywrócić wszystkie wyskakujące transakcje i wszystkie zapisane stany fragmentów:

Kotlin

supportFragmentManager.restoreBackStack("replacement")

Java

supportFragmentManager.restoreBackStack("replacement");

Podaj zależności we fragmentach

Przy dodawaniu fragmentu możesz utworzyć go ręcznie, Dodaj ją do FragmentTransaction.

Kotlin

fragmentManager.commit {
    // Instantiate a new instance before adding
    val myFragment = ExampleFragment()
    add(R.id.fragment_view_container, myFragment)
    setReorderingAllowed(true)
}

Java

// Instantiate a new instance before adding
ExampleFragment myFragment = new ExampleFragment();
fragmentManager.beginTransaction()
    .add(R.id.fragment_view_container, myFragment)
    .setReorderingAllowed(true)
    .commit();

Gdy zatwierdzisz transakcję dotyczącą fragmentu, wystąpienie fragmentu jest używana przez Ciebie instancja. Jednak w trakcie zmianie konfiguracji, działania i wszystkich jego fragmentów są niszczone, a następnie odtwarzane za pomocą najbardziej odpowiednia Materiały dotyczące Androida FragmentManager zarządza tym wszystkim za Ciebie: odtwarza wystąpienia Twoich fragmentów, przyłącza je do hosta i odtwarza tylny stos stanu.

Domyślnie FragmentManager używa FragmentFactory, które udostępnia platformę do utworzenia nowego wystąpienia fragmentu. Ten domyślna fabryka używa refleksji, aby znaleźć i wywołać konstruktor bez argumentu dla danego fragmentu. Oznacza to, że nie można użyć tej domyślnej opcji fabrycznej, aby podaj zależności fragmentu. Oznacza to również, że wszystkie niestandardowe konstruktora użytego do utworzenia fragmentu za pierwszym razem nie jest używany. podczas odtwarzania.

Aby podać zależności fragmentu lub użyć konstruktora, zamiast tego utwórz niestandardową podklasę FragmentFactory. a następnie zastąp FragmentFactory.instantiate Następnie możesz zastąpić domyślną wersję fabryczne urządzenia FragmentManager za pomocą niestandardową fabrykę, która jest następnie wykorzystywana do utworzenia wystąpienia fragmentów.

Załóżmy, że w organizacji DessertsFragment jest odpowiedzialny za wyświetlanie popularne desery w Twoim mieście, a DessertsFragment zależność od klasy DessertsRepository, która udostępnia informacji, które są niezbędne do wyświetlania użytkownikowi właściwego interfejsu.

Możesz określić, że DessertsFragment wymaga DessertsRepository w swoim konstruktorze.

Kotlin

class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() {
    ...
}

Java

public class DessertsFragment extends Fragment {
    private DessertsRepository dessertsRepository;

    public DessertsFragment(DessertsRepository dessertsRepository) {
        super();
        this.dessertsRepository = dessertsRepository;
    }

    // Getter omitted.

    ...
}

Prosta implementacja interfejsu FragmentFactory może wyglądać podobnie do: w następujący sposób.

Kotlin

class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment =
            when (loadFragmentClass(classLoader, className)) {
                DessertsFragment::class.java -> DessertsFragment(repository)
                else -> super.instantiate(classLoader, className)
            }
}

Java

public class MyFragmentFactory extends FragmentFactory {
    private DessertsRepository repository;

    public MyFragmentFactory(DessertsRepository repository) {
        super();
        this.repository = repository;
    }

    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
        Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className);
        if (fragmentClass == DessertsFragment.class) {
            return new DessertsFragment(repository);
        } else {
            return super.instantiate(classLoader, className);
        }
    }
}

Ten przykładowy podklasa FragmentFactory zastępuje klasę instantiate() , aby zapewnić niestandardową logikę tworzenia fragmentów dla DessertsFragment. Inne klasy fragmentów są obsługiwane przez domyślne zachowanie FragmentFactory do super.instantiate().

Następnie możesz oznaczyć urządzenie MyFragmentFactory jako fabryczne, które będzie używane podczas tworząc fragmenty aplikacji, ustawiając właściwość na FragmentManager Musisz ustawić tę właściwość przed super.onCreate(), aby mieć pewność, że MyFragmentFactory jest używany, gdy na ich odtworzenie.

Kotlin

class MealActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance())
        super.onCreate(savedInstanceState)
    }
}

Java

public class MealActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        DessertsRepository repository = DessertsRepository.getInstance();
        getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository));
        super.onCreate(savedInstanceState);
    }
}

Ustawienie FragmentFactory w aktywności zastępuje fragment w całej hierarchii fragmentów aktywności. Innymi słowy, element childFragmentManager dowolnego dodanego przez Ciebie fragmentu podrzędnego używa parametru niestandardowego fabryka fragmentów skonfigurowana tutaj, chyba że zostanie zastąpiona na niższym poziomie.

Testowanie za pomocą FragmentFactory

Przetestuj fragmenty w ramach jednej architektury aktywności izolacji użytkowników za pomocą FragmentScenario zajęcia. Nie można polegać na niestandardowej logice onCreate w tagu activity, możesz zamiast tego przekazać FragmentFactory jako argument jak w poniższym przykładzie:

// Inside your test
val dessertRepository = mock(DessertsRepository::class.java)
launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment {
    // Test Fragment logic
}

Szczegółowe informacje o procesie testowania i pełne przykłady znajdziesz w artykule zapoznaj się z sekcją Testowanie fragmentów.