Poruszanie się między fragmentami za pomocą animacji

Interfejs Fragment API umożliwia korzystanie z efektów ruchu i przekształceń na 2 sposoby aby wizualnie połączyć fragmenty podczas nawigacji. Jednym z nich jest Struktura animacji, która wykorzystuje Animation i Animator a drugi to platforma przejścia, która obejmuje przejściach elementów wspólnych.

Możesz określić niestandardowe efekty dla wprowadzania i kończenia fragmentów oraz dla przejścia wspólnych elementów między fragmentami.

  • Efekt Enter określa sposób, w jaki fragment wchodzi na ekran. Przykład: można utworzyć efekt polegający na wsunięciu fragmentu od krawędzi ekranu, gdy przechodzisz do niego.
  • Efekt wyjścia określa sposób opuszczenia ekranu przez fragment. Przykład: można utworzyć efekt zanikania fragmentów w trakcie opuszczania aplikacji .
  • Przejście ze wspólnego elementu określa sposób udostępniania widoku danych między między nimi zostaną umieszczone dwa fragmenty. Na przykład obraz wyświetlany we fragmencie ImageView przechodzi do fragmentu B raz B .

ustawiania animacji,

Najpierw musisz utworzyć animacje dla efektów wejścia i zakończenia, które są uruchamianych przy przejściu do nowego fragmentu. Animacje możesz zdefiniować jako zasobów animacji starszych. Za pomocą tych zasobów możesz określić, jak fragmenty mają być obracane, rozciągane, zanikać i poruszają się w trakcie animacji. Na przykład możesz potrzebować bieżącego fragmentu, zanikanie i nowy fragment do przesunięcia od prawej krawędzi ekranu, jak widać na ilustracji 1.

Otwórz animację i zamknij ją. Bieżący fragment zanika podczas
            następny fragment przesuwa się od prawej strony.
Rysunek 1. Otwórz animację i zamknij ją. Bieżący fragment zanika, gdy następny fragment przesuwa się od prawej strony.

Te animacje można zdefiniować w katalogu res/anim:

<!-- res/anim/fade_out.xml -->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromAlpha="1"
    android:toAlpha="0" />
<!-- res/anim/slide_in.xml -->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromXDelta="100%"
    android:toXDelta="0%" />

Możesz też określić animacje dla uruchamianych efektów wejścia i wyjścia. może się zdarzyć, gdy użytkownik naciśnie w górę lub w górę Przycisk Wstecz. Są to animacje popEnter i popExit. Dla: np. gdy użytkownik wróci do poprzedniego ekranu, możesz ustawić bieżący fragment, aby odsunąć prawą krawędź ekranu i poprzednią fragment, który ma zanikać.

animacje popEnter i popExit. Bieżący fragment zsuwa się
            ekran w prawo, gdy poprzedni fragment się zmienia.
Rysunek 2. popEnter i Animacje: popExit. Bieżący fragment przesuwa się z ekranu w prawo, podczas gdy poprzedni fragment się zmienia.

Te animacje można zdefiniować w ten sposób:

<!-- res/anim/slide_out.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromXDelta="0%"
    android:toXDelta="100%" />
<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromAlpha="0"
    android:toAlpha="1" />

Po zdefiniowaniu animacji możesz ich używać za pomocą wywołania FragmentTransaction.setCustomAnimations() przekazywanie zasobów animacji według ich identyfikatorów zasobów, jak widać w tagu następujący przykład:

Kotlin

supportFragmentManager.commit {
    setCustomAnimations(
        R.anim.slide_in, // enter
        R.anim.fade_out, // exit
        R.anim.fade_in, // popEnter
        R.anim.slide_out // popExit
    )
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

Java

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setCustomAnimations(
        R.anim.slide_in,  // enter
        R.anim.fade_out,  // exit
        R.anim.fade_in,   // popEnter
        R.anim.slide_out  // popExit
    )
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

Ustaw przejścia

Przejścia mogą też służyć do definiowania efektów wejścia i wyjścia. Te przejścia można definiować w plikach zasobów XML. Możesz na przykład: chcesz, aby bieżący fragment zanikał, a nowy fragment został przesunięty z przy prawej krawędzi ekranu. Przejścia te można zdefiniować w ten sposób:

<!-- res/transition/fade.xml -->
<fade xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"/>
<!-- res/transition/slide_right.xml -->
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:slideEdge="right" />

Po zdefiniowaniu opcji przejścia zastosuj je, wywołując setEnterTransition() na wpisywanym fragmencie setExitTransition() na wyjściu z fragmentu, przekazując zwiększone zasoby przejścia według identyfikatora zasobu, jak widać w tym przykładzie:

Kotlin

class FragmentA : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val inflater = TransitionInflater.from(requireContext())
        exitTransition = inflater.inflateTransition(R.transition.fade)
    }
}

class FragmentB : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val inflater = TransitionInflater.from(requireContext())
        enterTransition = inflater.inflateTransition(R.transition.slide_right)
    }
}

Java

public class FragmentA extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TransitionInflater inflater = TransitionInflater.from(requireContext());
        setExitTransition(inflater.inflateTransition(R.transition.fade));
    }
}

public class FragmentB extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TransitionInflater inflater = TransitionInflater.from(requireContext());
        setEnterTransition(inflater.inflateTransition(R.transition.slide_right));
    }
}

Obsługa fragmentów Przejście na AndroidaX. Fragmenty obsługują też procesu przenoszenia zasad, zalecamy przejście na AndroidaX, ponieważ są one obsługiwane w interfejsach API na poziomie 14 i nowszych oraz zawierają poprawki błędów, których nie było w starszych przejścia na nową platformę.

Używanie wspólnych przejść elementów

W ramach Zasad przejścia. przejścia między wspólnymi elementami określają sposób przechodzenia odpowiednich widoków między nimi dwóch fragmentów podczas przejścia z fragmentem. Możesz na przykład dodać obraz wyświetlany w polu ImageView na fragmencie A, przechodząc do fragmentu B gdy widoczny jest symbol B, jak widać na rysunku 3.

Przejście fragmentu ze wspólnym elementem.
Rysunek 3. Przejście fragmentu ze wspólnym elementem.

Ogólnie rzecz biorąc, aby utworzyć przejście fragmentu ze wspólnymi elementami:

  1. Do każdego udostępnionego widoku elementu przypisz niepowtarzalną nazwę przejścia.
  2. Dodaj wspólne widoki elementów i nazwy przejść do FragmentTransaction
  3. Ustaw animację przejścia elementu.

Po pierwsze musisz przypisać niepowtarzalną nazwę przejścia do każdego udostępnionego widoku elementów. umożliwia mapowanie widoków z jednego fragmentu na drugi. Ustaw nazwy przejścia w udostępnianych elementach w każdym układzie fragmentów za pomocą funkcji ViewCompat.setTransitionName() , który zapewnia zgodność z interfejsem API na poziomie 14 i wyższym. Na przykład nazwa przejścia dla elementu ImageView we fragmentach A i B można przypisać w następujący sposób:

Kotlin

class FragmentA : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        val itemImageView = view.findViewById<ImageView>(R.id.item_image)
        ViewCompat.setTransitionName(itemImageView, item_image)
    }
}

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        val heroImageView = view.findViewById<ImageView>(R.id.hero_image)
        ViewCompat.setTransitionName(heroImageView, hero_image)
    }
}

Java

public class FragmentA extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        ImageView itemImageView = view.findViewById(R.id.item_image);
        ViewCompat.setTransitionName(itemImageView, item_image);
    }
}

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        ImageView heroImageView = view.findViewById(R.id.hero_image);
        ViewCompat.setTransitionName(heroImageView, hero_image);
    }
}

Aby uwzględnić udostępnione elementy w przejściu fragmentów, tag FragmentTransaction musi wiedzieć, jak wyświetlenia każdego udostępnionego elementu są mapowane na podstawie jednego fragment do następnego. Dodaj każdy z udostępnionych elementów do FragmentTransaction przez połączenie FragmentTransaction.addSharedElement(), i nazwy przejścia odpowiedniego widoku w następny fragment, jak w tym przykładzie:

Kotlin

val fragment = FragmentB()
supportFragmentManager.commit {
    setCustomAnimations(...)
    addSharedElement(itemImageView, hero_image)
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

Java

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setCustomAnimations(...)
    .addSharedElement(itemImageView, hero_image)
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

Aby określić, w jaki sposób udostępniane elementy będą przechodzić między fragmentami, musisz ustawić przejście Enter dla fragmentu przeszedł na stronę. Zadzwoń do nas Fragment.setSharedElementEnterTransition() w metodzie onCreate() fragmentu, jak widać w tym przykładzie:

Kotlin

class FragmentB : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedElementEnterTransition = TransitionInflater.from(requireContext())
             .inflateTransition(R.transition.shared_image)
    }
}

Java

public class FragmentB extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Transition transition = TransitionInflater.from(requireContext())
            .inflateTransition(R.transition.shared_image);
        setSharedElementEnterTransition(transition);
    }
}

Przejście shared_image jest zdefiniowane w ten sposób:

<!-- res/transition/shared_image.xml -->
<transitionSet>
    <changeImageTransform />
</transitionSet>

Wszystkie podklasy obiektu Transition są obsługiwane jako udostępnione przejścia elementów. Jeśli Jeśli chcesz utworzyć niestandardowy Transition, zobacz Utwórz niestandardową animację przejścia. W poprzednim przykładzie model changeImageTransform jest jednym z dostępnych gotowych tłumaczeń, których możesz używać. Dodatkowe Transition podklas w dokumentacji interfejsu API dla argumentów Zajęcia: Transition.

Domyślnie przejście do udostępnianego elementu służy też jako przejście Return dla udostępnionych elementów. Przejście powrotne określa, elementy udostępnione przechodzą z powrotem do poprzedniego fragmentu, gdy fragment transakcja jest wysunięta na tylny stos. Jeśli chcesz podać inny przejście powrotne, możesz to zrobić za pomocą funkcji Fragment.setSharedElementReturnTransition() w metodzie onCreate() fragmentu.

Przewidywana zgodność wsteczna

Z prognozowania wstecznego możesz korzystać w przypadku wielu animacji zawierających fragmenty, ale nie wszystkich. Stosując prognozowanie wsteczne, pamiętaj o tych kwestiach:

  • Importuj Transitions 1.5.0 lub nowsze oraz Fragments 1.7.0 lub nowsze.
  • Klasa i podklasy Animator oraz biblioteka przenoszenia AndroidaX to: obsługiwane.
  • Biblioteka klas Animation i biblioteka Transition nie są obsługiwane.
  • Animacje fragmentów prognozujących działają tylko na urządzeniach z Androidem 14 lub wyższe.
  • setCustomAnimations, setEnterTransition, setExitTransition setReenterTransition, setReturnTransition, setSharedElementEnterTransition i setSharedElementReturnTransition są z przewidywaniami.

Więcej informacji: Dodano obsługę animacji wstecznych.

Odkładanie przenoszenia

W niektórych przypadkach trzeba odroczyć przejście fragmentu na w krótkim czasie. Na przykład musisz poczekać, aż wszystkie wyświetlenia we fragmencie wejściowym zostały zmierzone i rozmieszczone w taki sposób, aby Android może dokładnie zarejestrować stan początkowy i końcowy przejścia.

Dodatkowo przeniesienie może być odroczone do wszystkie niezbędne dane są wczytywane. Możesz na przykład poczekać do dla udostępnionych elementów wczytano obrazy. W przeciwnym razie przeniesienie może może powodować jaranie, jeśli obraz wczytuje się podczas przejścia lub po nim.

Aby opóźnić przejście, najpierw upewnij się, że fragment transakcja umożliwia zmianę stanu zmian stanu fragmentu. Aby umożliwić zmianę kolejności zmiany stanu fragmentów, wywołanie FragmentTransaction.setReorderingAllowed() jak w tym przykładzie:

Kotlin

val fragment = FragmentB()
supportFragmentManager.commit {
    setReorderingAllowed(true)
    setCustomAnimation(...)
    addSharedElement(view, view.transitionName)
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

Java

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setReorderingAllowed(true)
    .setCustomAnimations(...)
    .addSharedElement(view, view.getTransitionName())
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

Aby opóźnić przejście, wywołaj polecenie Fragment.postponeEnterTransition() w metodzie onViewCreated() fragmentu wprowadzającego:

Kotlin

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        postponeEnterTransition()
    }
}

Java

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        postponeEnterTransition();
    }
}

Po załadowaniu danych i przygotowaniu się do rozpoczęcia przenoszenia wywołaj polecenie Fragment.startPostponedEnterTransition() W poniższym przykładzie użyto parametru Glide, aby wczytać obraz w udostępniony ImageView, odkładając odpowiednie przejście do obrazu ładowanie zostało zakończone.

Kotlin

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        Glide.with(this)
            .load(url)
            .listener(object : RequestListener<Drawable> {
                override fun onLoadFailed(...): Boolean {
                    startPostponedEnterTransition()
                    return false
                }

                override fun onResourceReady(...): Boolean {
                    startPostponedEnterTransition()
                    return false
                }
            })
            .into(headerImage)
    }
}

Java

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        Glide.with(this)
            .load(url)
            .listener(new RequestListener<Drawable>() {
                @Override
                public boolean onLoadFailed(...) {
                    startPostponedEnterTransition();
                    return false;
                }

                @Override
                public boolean onResourceReady(...) {
                    startPostponedEnterTransition();
                    return false;
                }
            })
            .into(headerImage)
    }
}

W przypadku powolnego połączenia internetowego użytkownika możesz potrzebują odroczonego przejścia, aby rozpoczęło się po określonym czasie, a nie niż czekanie, aż wszystkie dane się załadują. W takiej sytuacji możesz zamiast tego zadzwoń Fragment.postponeEnterTransition(long, TimeUnit) w metodzie onViewCreated() fragmentu, przekazując czas trwania i jednostkę czasu. Przełożony harmonogram rozpocznie się automatycznie, gdy w określonym czasie.

Używaj przejść z elementami udostępnionych za pomocą komponentu RecyclerView

Przełożone przejście nie powinno się rozpocząć, dopóki nie wszystkie wyświetlenia na etapie fragment został zmierzony i rozmieszczony. Jeśli używasz tagu RecyclerView, musisz poczekać aby wszystkie dane zostały załadowane i aby elementy RecyclerView były gotowe do rysowania. przed rozpoczęciem przenoszenia. Oto przykład:

Kotlin

class FragmentA : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        postponeEnterTransition()

        // Wait for the data to load
        viewModel.data.observe(viewLifecycleOwner) {
            // Set the data on the RecyclerView adapter
            adapter.setData(it)
            // Start the transition once all views have been
            // measured and laid out
            (view.parent as? ViewGroup)?.doOnPreDraw {
                startPostponedEnterTransition()
            }
        }
    }
}

Java

public class FragmentA extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        postponeEnterTransition();

        final ViewGroup parentView = (ViewGroup) view.getParent();
        // Wait for the data to load
        viewModel.getData()
            .observe(getViewLifecycleOwner(), new Observer<List<String>>() {
                @Override
                public void onChanged(List<String> list) {
                    // Set the data on the RecyclerView adapter
                    adapter.setData(it);
                    // Start the transition once all views have been
                    // measured and laid out
                    parentView.getViewTreeObserver()
                        .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                            @Override
                            public boolean onPreDraw(){
                                parentView.getViewTreeObserver()
                                        .removeOnPreDrawListener(this);
                                startPostponedEnterTransition();
                                return true;
                            }
                    });
                }
        });
    }
}

Zwróć uwagę, że ViewTreeObserver.OnPreDrawListener jest ustawiony w elemencie nadrzędnym widoku fragmentów. Ma to na celu zapewnienie, że wszystkie wyświetlenia fragmentu zostały zmierzone i rozmieszczone, więc są gotowe do może być rysowana przed rozpoczęciem opóźnionego przejścia.

Kolejna kwestia do rozważenia przy korzystaniu z przejścia udostępnionych elementów z tagiem RecyclerView polega na tym, że nie można określić nazwy przejścia w polu Układ XML RecyclerView elementu, ponieważ współdzielisz dowolną liczbę elementów i tego układu. Aby tag animacja przejścia ma prawidłowy widok.

Każdemu elementowi możesz nadać unikalną nazwę przejścia: i przypisuje je, gdy zasada ViewHolder jest powiązana. Na przykład, jeśli dane dot. Każdy element ma niepowtarzalny identyfikator, którego można użyć jako nazwy przejścia, w tym przykładzie:

Kotlin

class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val image = itemView.findViewById<ImageView>(R.id.item_image)

    fun bind(id: String) {
        ViewCompat.setTransitionName(image, id)
        ...
    }
}

Java

public class ExampleViewHolder extends RecyclerView.ViewHolder {
    private final ImageView image;

    ExampleViewHolder(View itemView) {
        super(itemView);
        image = itemView.findViewById(R.id.item_image);
    }

    public void bind(String id) {
        ViewCompat.setTransitionName(image, id);
        ...
    }
}

Dodatkowe materiały

Aby dowiedzieć się więcej o przejściach fragmentów, zapoznaj się z tymi dodatkowymi informacjami i zasobami Google Cloud.

Próbki

Posty na blogu