Mithilfe von Animationen zwischen Fragmenten wechseln

Die Fragment API bietet zwei Möglichkeiten, Fragmente mithilfe von Bewegungseffekten und Transformationen während der Navigation visuell zu verbinden. Eines davon ist das Animation Framework, das sowohl Animation als auch Animator verwendet. Die andere ist das Transition Framework, das Übergänge gemeinsam verwendeter Elemente enthält.

Sie können benutzerdefinierte Effekte für das Eingeben und Schließen von Fragmenten sowie für den Übergang von gemeinsam genutzten Elementen zwischen Fragmenten festlegen.

  • Der Eingabeeffekt bestimmt, wie ein Fragment auf dem Bildschirm erscheint. Sie können beispielsweise einen Effekt erstellen, bei dem das Fragment vom Bildschirmrand her ins Bild geschoben wird, wenn Sie dorthin gehen.
  • Ein Exit-Effekt bestimmt, wie ein Fragment den Bildschirm verlässt. Beispielsweise können Sie einen Effekt erstellen, mit dem das Fragment ausgeblendet wird, wenn Sie es verlassen.
  • Ein Übergang gemeinsam genutzter Elemente bestimmt, wie eine von zwei Fragmenten gemeinsam genutzte Ansicht zwischen ihnen verschoben wird. Beispielsweise geht ein Bild, das in Fragment A in ImageView angezeigt wird, zu Fragment B über, sobald B sichtbar wird.

Animationen festlegen

Zuerst müssen Sie Animationen für die Eingabe- und Exit-Effekte erstellen, die beim Navigieren zu einem neuen Fragment ausgeführt werden. Sie können Animationen als Animationsressourcen definieren. Mit diesen Ressourcen können Sie festlegen, wie Fragmente während der Animation gedreht, gestreckt, verblasst und verschoben werden sollen. Vielleicht möchten Sie, dass das aktuelle Fragment ausgeblendet und das neue Fragment vom rechten Bildschirmrand her in den Bildschirm verschoben wird (siehe Abbildung 1).

Animationen starten und beenden. Das aktuelle Fragment wird ausgeblendet, während das nächste von rechts hineingeschoben wird.
Abbildung 1: Animationen starten und beenden. Das aktuelle Fragment wird ausgeblendet, während das nächste von rechts ins Bild gleitet.

Diese Animationen können im Verzeichnis res/anim definiert werden:

<!-- 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%" />

Sie können auch Animationen für die Eingabe- und Exit-Effekte festlegen, die ausgeführt werden, wenn der Back-Stack aufgeklappt wird. Dies kann passieren, wenn der Nutzer auf die Schaltfläche „Nach oben“ oder „Zurück“ tippt. Diese werden als popEnter- und popExit-Animationen bezeichnet. Wenn ein Nutzer beispielsweise zu einem vorherigen Bildschirm zurückkehrt, kann es sein, dass das aktuelle Fragment vom rechten Bildschirmrand entfernt und das vorherige Fragment eingeblendet wird.

„popEnter“- und „popExit“-Animationen. Das aktuelle Fragment wird aus dem Bildschirm nach rechts geschoben, während das vorherige Fragment eingeblendet wird.
Abbildung 2: popEnter- und popExit-Animationen. Das aktuelle Fragment wird aus dem Bildschirm nach rechts geschoben, während das vorherige Fragment eingeblendet wird.

Diese Animationen können so definiert werden:

<!-- 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" />

Nachdem Sie die Animationen definiert haben, können Sie sie verwenden, indem Sie FragmentTransaction.setCustomAnimations() aufrufen und Ihre Animationsressourcen anhand ihrer Ressourcen-ID übergeben, wie im folgenden Beispiel gezeigt:

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

Übergänge festlegen

Sie können Übergänge auch verwenden, um Eingabe- und Exit-Effekte zu definieren. Diese Übergänge können in XML-Ressourcendateien definiert werden. So lässt sich z. B. das aktuelle Fragment verblassen und das neue vom rechten Bildschirmrand einblenden. Diese Übergänge können so definiert werden:

<!-- 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" />

Nachdem Sie die Übergänge definiert haben, wenden Sie sie an, indem Sie setEnterTransition() für das eingegebene Fragment und setExitTransition() für das verlassene Fragment aufrufen. Übergeben Sie dann die aufgeblähten Übergangsressourcen anhand ihrer Ressourcen-ID, wie im folgenden Beispiel gezeigt:

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

Fragmente unterstützen AndroidX-Übergänge. Fragmente unterstützen zwar auch Framework-Übergänge, aber wir empfehlen dringend die Verwendung von AndroidX-Übergängen, da sie ab API-Level 14 unterstützt werden und Fehlerkorrekturen enthalten, die in älteren Versionen von Framework-Übergängen nicht vorhanden sind.

Übergänge für gemeinsam genutzte Elemente verwenden

Als Teil des Transition Framework bestimmen Übergänge gemeinsam genutzter Elemente, wie sich entsprechende Ansichten während eines Fragmentübergangs zwischen zwei Fragmenten bewegen. Angenommen, ein Bild, das in einem ImageView in Fragment A angezeigt wird, soll zu Fragment B übergehen, sobald B sichtbar wird (siehe Abbildung 3).

Ein Fragmentübergang mit einem gemeinsamen Element.
Abbildung 3: Ein Fragmentübergang mit einem gemeinsamen Element.

So nehmen Sie einen Fragmentübergang bei gemeinsam genutzten Elementen vor:

  1. Weisen Sie jeder Ansicht geteilter Elemente einen eindeutigen Übergangsnamen zu.
  2. Fügen Sie der FragmentTransaction freigegebene Elementansichten und Übergangsnamen hinzu.
  3. Animation für den Übergang zu gemeinsam genutzten Elementen festlegen

Zuerst müssen Sie jeder Ansicht gemeinsamer Elemente einen eindeutigen Übergangsnamen zuweisen, damit die Ansichten von einem Fragment zum nächsten zugeordnet werden können. Legen Sie mithilfe von ViewCompat.setTransitionName() für gemeinsam genutzte Elemente in jedem Fragmentlayout einen Übergangsnamen fest. Dadurch ist die Kompatibilität mit API-Level 14 und höher möglich. Beispielsweise kann der Übergangsname für ein ImageView in den Fragmenten A und B so zugewiesen werden:

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

Damit Ihre gemeinsam genutzten Elemente in den Fragmentübergang aufgenommen werden, muss das FragmentTransaction wissen, wie die Ansichten eines geteilten Elements von einem Fragment zum nächsten dargestellt werden. Fügen Sie alle gemeinsam genutzten Elemente zu FragmentTransaction hinzu, indem Sie FragmentTransaction.addSharedElement() aufrufen und dabei die Ansicht und den Übergangsnamen der entsprechenden Ansicht im nächsten Fragment übergeben, wie im folgenden Beispiel gezeigt:

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

Um anzugeben, wie die gemeinsam genutzten Elemente von einem Fragment zum nächsten übergehen, müssen Sie für das zu navigierende Fragment den Übergang enter festlegen. Rufen Sie Fragment.setSharedElementEnterTransition() in der Methode onCreate() des Fragments auf, wie im folgenden Beispiel gezeigt:

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

Der Übergang shared_image ist so definiert:

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

Alle abgeleiteten Klassen von Transition werden als Übergänge gemeinsam verwendeter Elemente unterstützt. Informationen zum Erstellen einer benutzerdefinierten Transition finden Sie unter Benutzerdefinierte Übergangsanimation erstellen. changeImageTransform wurde im vorherigen Beispiel verwendet und ist eine der verfügbaren vordefinierten Übersetzungen, die Sie verwenden können. Weitere untergeordnete Transition-Klassen finden Sie in der API-Referenz für die Klasse Transition.

Standardmäßig wird der Übergang zum Wechsel des gemeinsamen Elements auch als return-Übergang für gemeinsam genutzte Elemente verwendet. Der Rückgabeübergang bestimmt, wie gemeinsam genutzte Elemente zum vorherigen Fragment zurückkehren, wenn die Fragmenttransaktion aus dem Back-Stack entfernt wird. Wenn Sie einen anderen Rückgabeübergang festlegen möchten, können Sie das mit Fragment.setSharedElementReturnTransition() in der Methode onCreate() des Fragments festlegen.

Vorhergesagte Rückenkompatibilität

Sie können die Vorhersage mit vielen, aber nicht allen fragmentübergreifenden Animationen verwenden. Beachten Sie bei der Implementierung der vorausschauenden Rückkehr Folgendes:

  • Importieren Sie Transitions 1.5.0 oder höher und Fragments 1.7.0 oder höher.
  • Die Animator-Klasse und -Unterklassen sowie die AndroidX-Übergangsbibliothek werden unterstützt.
  • Die Klasse Animation und die Framework Transition-Bibliothek werden nicht unterstützt.
  • Animationen mit prädiktiven Fragmenten funktionieren nur auf Geräten mit Android 14 oder höher.
  • setCustomAnimations, setEnterTransition, setExitTransition, setReenterTransition, setReturnTransition, setSharedElementEnterTransition und setSharedElementReturnTransition werden für vorausschauende Antworten unterstützt.

Weitere Informationen findest du unter Unterstützung für vorausschauende Zurück-Animationen hinzufügen.

Umstellungen verschieben

In einigen Fällen müssen Sie den Fragmentübergang möglicherweise für kurze Zeit verschieben. Beispielsweise müssen Sie möglicherweise warten, bis alle Ansichten im eingehenden Fragment gemessen und angeordnet sind, damit Android den Start- und Endzustand für den Übergang genau erfassen kann.

Außerdem muss die Umstellung möglicherweise verschoben werden, bis einige erforderliche Daten geladen wurden. Zum Beispiel müssen Sie möglicherweise warten, bis Bilder für gemeinsam genutzte Elemente geladen sind. Andernfalls kann der Übergang schiefgehen, wenn das Laden eines Bildes während oder nach dem Übergang abgeschlossen ist.

Um einen Übergang zu verschieben, müssen Sie zuerst dafür sorgen, dass die Fragmenttransaktion die Neuanordnung von Fragmentstatusänderungen zulässt. Damit Änderungen des Fragmentstatus neu angeordnet werden können, rufen Sie FragmentTransaction.setReorderingAllowed() auf, wie im folgenden Beispiel gezeigt:

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

Wenn Sie den Übergang zur Eingabetaste verschieben möchten, rufen Sie in der Methode onViewCreated() des eingegebenen Fragments Fragment.postponeEnterTransition() auf:

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

Sobald Sie die Daten geladen haben und bereit sind, mit der Umstellung zu beginnen, rufen Sie Fragment.startPostponedEnterTransition() auf. Im folgenden Beispiel wird die Glide-Bibliothek verwendet, um ein Bild in ein freigegebenes ImageView zu laden. Der entsprechende Übergang wird verschoben, bis das Bild geladen ist.

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

Wenn es sich um Fälle wie eine langsame Internetverbindung eines Nutzers handelt, müssen Sie möglicherweise nicht warten, bis alle Daten geladen sind, und der verzögerte Übergang sollte nach einer bestimmten Zeit beginnen. In diesen Fällen können Sie stattdessen Fragment.postponeEnterTransition(long, TimeUnit) in der Methode onViewCreated() des eingegebenen Fragments aufrufen und dabei die Dauer und die Zeiteinheit übergeben. Das Verschieben wird dann automatisch gestartet, sobald die angegebene Zeit verstrichen ist.

Übergänge für gemeinsam genutzte Elemente mit einem RecyclerView verwenden

Übergänge mit verschobener Eingabe sollten erst beginnen, wenn alle Ansichten im Eingabe-Fragment gemessen und angelegt wurden. Wenn Sie ein RecyclerView-Objekt verwenden, müssen Sie warten, bis alle Daten geladen sind und die RecyclerView-Elemente gezeichnet werden können, bevor Sie mit dem Übergang beginnen. Beispiel:

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

Wie Sie sehen, wurde für das übergeordnete Element der Fragmentansicht ein ViewTreeObserver.OnPreDrawListener festgelegt. Dadurch wird sichergestellt, dass alle Ansichten des Fragments gemessen und angelegt wurden und daher gezeichnet werden können, bevor der Übergang des Fragments gestartet wird.

Bei der Verwendung von Übergängen gemeinsam verwendeter Elemente mit einem RecyclerView ist außerdem zu beachten, dass der Übergangsname nicht im XML-Layout des RecyclerView-Elements festgelegt werden kann, da dieses Layout von einer beliebigen Anzahl von Elementen verwendet wird. Sie müssen einen eindeutigen Übergangsnamen zuweisen, damit in der Übergangsanimation die richtige Ansicht verwendet wird.

Sie können dem freigegebenen Element jedes Elements einen eindeutigen Übergangsnamen geben. Dazu weisen Sie ihnen zu, wenn ViewHolder gebunden ist. Wenn beispielsweise die Daten für jedes Element eine eindeutige ID enthalten, kann diese wie im folgenden Beispiel als Übergangsname verwendet werden:

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

Weitere Informationen

Weitere Informationen zu Fragmentübergängen finden Sie in den folgenden zusätzlichen Ressourcen.

Produktproben

Blogposts