Ich versuche, den folgenden Effekt mit FragmentTransaction.setCustomAnimations zu erzielen.
- Fragment A wird angezeigt
- Ersetzen Sie Fragment A durch Fragment B. Fragment A sollte während des Austauschs sichtbar bleiben. Fragment B sollte von rechts hineingleiten. Fragment B sollte über die Oberseite von Fragment A gleiten.
Ich habe kein Problem damit, die Folie im Animations-Setup zu erhalten. Mein Problem ist, dass ich nicht herausfinden kann, wie Fragment A dort bleiben kann, wo es ist, und UNTER Fragment B sein kann, während die Folie in der Animation ausgeführt wird. Egal was ich mache, es scheint, dass Fragment A oben ist.
Wie kann ich das erreichen?
Hier ist der FragmentTransaction-Code:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.nothing, R.anim.nothing,
R.anim.slide_out_right);
ft.replace(R.id.fragment_content, fragment, name);
ft.addToBackStack(name);
ft.commit();
Wie Sie sehen können, habe ich eine Animation R.anim.nothing für die "out" -Animation definiert, da ich eigentlich nicht möchte, dass Fragment A etwas anderes tut, als nur dort zu bleiben, wo es während der Transaktion ist.
Hier sind die Animationsressourcen:
slide_in_right.xml
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="100%p"
android:toXDelta="0"
android:zAdjustment="top" />
nothing.xml
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="1.0"
android:zAdjustment="bottom" />
quelle
Antworten:
Update (16. Juni 2020)
Ausgehend von der Fragmentbibliothek wird
1.2.0
empfohlen, dieses ProblemFragmentContainerView
mit zu behebenFragmentTransaction.setCustomAnimations()
.Laut Dokumentation :
Schritte zur Behebung dieses Problems sind:
androidx.fragment:fragment:1.2.0
.<fragment>
,<FrameLayout>
, oder) durch<androidx.fragment.app.FragmentContainerView>
;FragmentTransaction.setCustomAnimations()
diese Option, um Ihre Fragmentübergänge zu animieren.Vorherige Antwort (19. November 2015)
Ausgehend von Lollipop können Sie die ÜbersetzungZ Ihres eingegebenen Fragments erhöhen. Es wird über dem Ausgang angezeigt.
Zum Beispiel:
@Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ViewCompat.setTranslationZ(getView(), 100.f); }
Wenn Sie den TranslationZ-Wert nur für die Dauer der Animation ändern möchten, sollten Sie Folgendes tun:
@Override public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) { Animation nextAnimation = AnimationUtils.loadAnimation(getContext(), nextAnim); nextAnimation.setAnimationListener(new Animation.AnimationListener() { private float mOldTranslationZ; @Override public void onAnimationStart(Animation animation) { if (getView() != null && enter) { mOldTranslationZ = ViewCompat.getTranslationZ(getView()); ViewCompat.setTranslationZ(getView(), 100.f); } } @Override public void onAnimationEnd(Animation animation) { if (getView() != null && enter) { ViewCompat.setTranslationZ(getView(), mOldTranslationZ); } } @Override public void onAnimationRepeat(Animation animation) { } }); return nextAnimation; }
quelle
onCreateAnimation
wir zum Überschreiben die Animation manuell über erstellen müssenAnimationUtils.loadAnimation
? Mir ist klar, wenn ich versuche,Animation
über super.onCreateAnimation zu erhalten , ist es null.Fragment.onCreateAnimation()
Methode standardmäßig nichts tut und nur null zurückgibt. Diese Methode ist nur dazu da, Ihnen die Möglichkeit zu geben, die Animation selbst zu erstellen. Wenn Sie sich die Methode ansehen, werdenFragmentManager.loadAnimation()
Sie feststellen, dass sie zuerst versucht, eine Animation abzurufen.fragment.onCreateAnimation()
Wenn sie null zurückgibt, erstellt der FragmentManager die Animation selbst.Ich weiß nicht, ob Sie noch eine Antwort benötigen, aber ich musste kürzlich das Gleiche tun und habe einen Weg gefunden, das zu tun, was Sie wollen.
Ich habe so etwas gemacht:
FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); MyFragment next = getMyFragment(); ft.add(R.id.MyLayout,next); ft.setCustomAnimations(R.anim.slide_in_right,0); ft.show(next); ft.commit();
Ich zeige mein Fragment in einem FrameLayout an.
Es funktioniert Geldstrafen, aber das ältere Fragment ist immer noch in meiner Ansicht. Ich lasse Android es verwalten, wie er es will, denn wenn ich sage:
ft.remove(myolderFrag);
Es wird während der Animation nicht angezeigt.
slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="150" android:fromXDelta="100%p" android:interpolator="@android:anim/linear_interpolator" android:toXDelta="0" /> </set>
quelle
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="150" android:fromXDelta="100%p" android:interpolator="@android:anim/linear_interpolator" android:toXDelta="0" /> </set>
overridePendingTransition(R.anim.my_animation, 0)
ist die Linie, nach der ich gesucht habe.Ich habe eine Lösung gefunden, die für mich funktioniert. Am Ende habe ich einen ViewPager mit einem FragmentStatePagerAdapter verwendet. Der ViewPager stellt das Wischverhalten bereit und der FragmentStatePagerAdapter tauscht die Fragmente aus. Der letzte Trick, um den Effekt zu erzielen, dass eine Seite "unter" der eingehenden Seite sichtbar ist, ist die Verwendung eines PageTransformers. Der PageTransformer überschreibt den Standardübergang des ViewPager zwischen Seiten. Hier ist ein Beispiel für einen PageTransformer, der den Effekt mit Übersetzung und einer geringen Skalierung auf der linken Seite erzielt.
public class ScalePageTransformer implements PageTransformer { private static final float SCALE_FACTOR = 0.95f; private final ViewPager mViewPager; public ScalePageTransformer(ViewPager viewPager) { this.mViewPager = viewPager; } @SuppressLint("NewApi") @Override public void transformPage(View page, float position) { if (position <= 0) { // apply zoom effect and offset translation only for pages to // the left final float transformValue = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR) + SCALE_FACTOR; int pageWidth = mViewPager.getWidth(); final float translateValue = position * -pageWidth; page.setScaleX(transformValue); page.setScaleY(transformValue); if (translateValue > -pageWidth) { page.setTranslationX(translateValue); } else { page.setTranslationX(0); } } } }
quelle
Nach weiteren Experimenten (daher ist dies meine zweite Antwort) scheint das Problem zu sein, dass R.anim.nothing "verschwinden" bedeutet, wenn wir eine andere Animation wollen, die explizit "bleiben" sagt. Die Lösung besteht darin, eine echte "Nichts tun" -Animation wie folgt zu definieren:
Datei erstellen
no_animation.xml
:<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:interpolator="@android:anim/linear_interpolator" android:fromXScale="1.0" android:toXScale="1.0" android:fromYScale="1.0" android:toYScale="1.0" android:duration="200" /> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="200" android:startOffset="200" /> </set>
Tun Sie jetzt einfach so, wie Sie es sonst tun würden:
getActivity().getSupportFragmentManager().beginTransaction() .setCustomAnimations(R.anim.slide_in_right, R.anim.no_animation) .replace(R.id.container, inFrag, FRAGMENT_TAG) .addToBackStack("Some text") .commit();
quelle
add
, verursachte jedoch Probleme bei der Verwendungreplace
. Das Ersetzen durch0
hat den Trick getan. Danke, @NineToeNerdIch habe eine alternative Lösung gefunden (nicht stark getestet), die ich eleganter finde als die bisherigen Vorschläge:
final IncomingFragment newFrag = new IncomingFragment(); newFrag.setEnterAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { clearSelection(); inFrag.clearEnterAnimationListener(); getFragmentManager().beginTransaction().remove(OutgoingFragment.this).commit(); } @Override public void onAnimationRepeat(Animation animation) { } }); getActivity().getSupportFragmentManager().beginTransaction() .setCustomAnimations(R.anim.slide_in_from_right, 0) .add(R.id.container, inFrag) .addToBackStack(null) .commit();
Dies wird aus einer inneren Klasse der OutgoingFragment-Klasse heraus aufgerufen.
Ein neues Fragment wird eingefügt, die Animation wird abgeschlossen und das alte Fragment wird entfernt.
In einigen Anwendungen kann es zu Speicherproblemen kommen, aber es ist besser, beide Fragmente unbegrenzt beizubehalten.
quelle
Fragment.setEnterAnimationListener(...)
... oder ist das eine benutzerdefinierte Funktion für dieses Fragment?Dies ist meine aktuelle Problemumgehung für alle Interessierten.
In der Funktion zum Hinzufügen des neuen
Fragment
:final Fragment toRemove = fragmentManager.findFragmentById(containerID); if (toRemove != null) { new Handler().postDelayed(new Runnable() { @Override public void run() { fragmentManager.beginTransaction().hide(toRemove).commit(); } }, getResources().getInteger(android.R.integer.config_mediumAnimTime) + 100); // Use whatever duration you chose for your animation for this handler // I added an extra 100 ms because the first transaction wasn't always // fast enough } fragmentManager.beginTransaction() .setCustomAnimations(enter, 0, 0, popExit).add(containerID, fragmentToAdd) .addToBackStack(tag).commit();
und in
onCreate
:final FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.addOnBackStackChangedListener( new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { Fragment current = fragmentManager.findFragmentById(containerID); if (current != null && current.isHidden()) { fragmentManager.beginTransaction().show(current).commit(); } } });
Ich würde eine Art AnimationListener anstelle eines Handlers oben bevorzugen, aber ich habe keine Möglichkeit gesehen, mit einer das Ende der Transaktionsanimation zu erkennen, die nicht an das Fragment gebunden war
onCreateAnimation()
. Alle Vorschläge / Änderungen mit einem geeigneten Hörer wäre dankbar.Ich werde darauf hinweisen, dass die
Fragment
s, die ich auf diese Weise hinzufüge, leicht sind, sodass es für mich kein Problem ist, sie zusammen mit dem Fragment, auf dem sie sich befinden, im Fragmentcontainer zu haben.Wenn Sie das Fragment entfernen möchten, können Sie es
fragmentManager.beginTransaction().remove(toRemove).commitAllowingStateLoss();
in dasHandler
'sRunnable
und in dasOnBackStackChangedListener
:// Use back stack entry tag to get the fragment Fragment current = getCurrentFragment(); if (current != null && !current.isAdded()) { fragmentManager.beginTransaction() .add(containerId, current, current.getTag()) .commitNowAllowingStateLoss(); }
Beachten Sie, dass die obige Lösung für das erste Fragment im Container nicht funktioniert (da es sich nicht im Backstack befindet). Sie müssten also eine andere Möglichkeit haben, dieses wiederherzustellen, und möglicherweise einen Verweis auf das erste Fragment irgendwie speichern. Wenn Sie den Backstack jedoch nicht verwenden und Fragmente immer manuell ersetzen, ist dies kein Problem. ODER Sie können alle Fragmente zum hinteren Stapel hinzufügen (einschließlich des ersten) und überschreiben
onBackPressed
, um sicherzustellen, dass Ihre Aktivität beendet wird, anstatt einen leeren Bildschirm anzuzeigen, wenn nur noch ein Fragment im hinteren Stapel vorhanden ist.EDIT: Ich habe die folgenden Funktionen entdeckt, die wahrscheinlich
FragmentTransaction.remove()
undFragmentTransaction.add()
höher ersetzen könnten :FragmentTransaction
. trennen () :FragmentTransaction
. attach () :quelle
Basierend auf jfrite Antwort, die meine Implementierungen anhängt
import android.content.res.Resources; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.view.ViewCompat; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.util.Log; public final class AnimationHelper { private AnimationHelper() { } private static String TAG = AnimationHelper.class.getSimpleName(); private static final float ELEVATION_WHILE_ENTER_ANIMATION_IS_RUNNING = 100f; private static final int RESTORE_ANIMATION_DELAY = 16; /** * When replacing fragments with animations, by default the new fragment is placed below the replaced fragment. This * method returns an animation object that sets high elevation at the beginning of the animation and resets the * elevation when the animation completes. The {@link Animation} object that is returned is not the actual object * that is used for the animating the fragment but the callbacks are called at the appropriate time. The method * {@link Fragment#onCreateAnimation(int, boolean, int)} by default returns null, therefor, this method can be used * as the return value for {@link Fragment#onCreateAnimation(int, boolean, int)} method although it can return * null. * @param enter True if fragment is 'entering'. * @param nextAnim Animation resource id that is about to play. * @param fragment The animated fragment. * @return If nextAnim is a valid resource id and 'enter' is true, returns an {@link Animation} object with the * described above behavior, otherwise returns null. */ @Nullable public static Animation increaseElevationWhileAnimating(boolean enter, int nextAnim, @NonNull Fragment fragment) { if (!enter || nextAnim == 0) { return null; } Animation nextAnimation; try { nextAnimation = AnimationUtils.loadAnimation(fragment.getContext(), nextAnim); } catch (Resources.NotFoundException e) { Log.e(TAG, "Can't find animation resource", e); return null; } nextAnimation.setAnimationListener(new Animation.AnimationListener() { private float oldTranslationZ; @Override public void onAnimationStart(Animation animation) { if (fragment.getView() != null && !fragment.isDetached()) { oldTranslationZ = ViewCompat.getTranslationZ(fragment.getView()); ViewCompat.setTranslationZ(fragment.getView(), ELEVATION_WHILE_ENTER_ANIMATION_IS_RUNNING); } } @Override public void onAnimationEnd(Animation animation) { if (fragment.getView() != null && !fragment.isDetached()) { fragment.getView().postDelayed(() -> { // Decreasing the elevation at the ned can cause some flickering because of timing issues, // Meaning that the replaced fragment did not complete yet the animation. Resting the animation // with a minor delay solves the problem. if (!fragment.isDetached()) { ViewCompat.setTranslationZ(fragment.getView(), oldTranslationZ); } }, RESTORE_ANIMATION_DELAY); } } @Override public void onAnimationRepeat(Animation animation) { } }); return nextAnimation; } }
Hier ist, wie ich den Helfer aus dem Fragment benutze.
@Override public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) { return AnimationHelper.increaseElevationWhileAnimating(enter, nextAnim, this); }
Hier ist, wie ich das Fragment mit Animation beginne
FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(R.anim.slide_in, R.anim.hold, R.anim.hold, R.anim.slide_out);
quelle
FragmentTransaction ft = ((AppCompatActivity) context).getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(0, R.anim.slide_out_to_right); if (!fragment.isAdded()) { ft.add(R.id.fragmentContainerFrameMyOrders, fragment); ft.show(fragment); } else ft.replace(R.id.fragmentContainerFrameMyOrders, fragment); ft.commit();
quelle
Fügen Sie dem Layout eine Ansicht hinzu.
Ich habe dem Fragment, das von unten in 30sp hineingleitet, eine Ansicht hinzugefügt, und es hat funktioniert.
Ich habe viele hier vorgeschlagene Lösungen ausprobiert. Hier sind der vollständige Code und die Ausgabe, die die gesamte Idee + das Hinzufügen von Höhen kombinieren .
Ausgabe:
Ohne Erhebung:
Mit Höhe:
Vollständiger Code:
Fügen Sie der Wurzel eine Höhe hinzu
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:elevation="30sp"> ---- ---- </RelativeLayout>
Wie schiebe ich das Fragment von unten hinein?
getSupportFragmentManager() .beginTransaction() .setCustomAnimations(R.anim.slide_in_bottom, R.anim.do_nothing, R.anim.do_nothing, R.anim.slide_out_bottom) .replace(R.id.fragmentContainer, currentFragment, "TAG") .addToBackStack("TAG") .commit();
Wie mache ich den Rückwärtsgang, wenn die Zurück-Taste gedrückt wird?
getSupportFragmentManager() .popBackStack();
R.anim.slide_in_bottom
<set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="500" android:fromYDelta="100%" android:toYDelta="0%"> </translate> </set>
R.anim.slide_out_bottom
<set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="500" android:fromYDelta="0%" android:toYDelta="100%"> </translate> </set>
R.anim.do_nothing
<set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:interpolator="@android:anim/linear_interpolator" android:fromXScale="1.0" android:toXScale="1.0" android:fromYScale="1.0" android:toYScale="1.0" android:duration="500"/> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:fromAlpha="1.0" android:toAlpha="1.0" android:duration="500" android:startOffset="500" /> </set>
quelle