Fix incorrect pop animation in multiple transactions
When a fragment is added by a pop and then removed in back to back
transactions, we should use a normal exit animation instead of a popExit
animation.
This change keeps track of the first Fragment being visibly removed
(remove, hide, or detach) in a set of FragmentTransactions. It passes
the animations of subsequent removing fragments to the one that will be
visible when the animation is executed. The Fragments are tracked by a
tag on their container.
Test: all tests pass, and tested in app
Bug: 111659726
Change-Id: I18eefc2fdb4fc4c27c467212cc2e68beb20ea00b
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index ced206d..4cf1fce 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1117,6 +1117,16 @@
int nextAnim = fragment.getNextAnim();
// Clear the Fragment animation
fragment.setNextAnim(0);
+ // We do not need to keep up with the removing Fragment after we get its next animation.
+ // If transactions do not allow reordering, this will always be true and the visible
+ // removing fragment will be cleared. If reordering is allowed, this will only be true
+ // after all records in a transaction have been executed and the visible removing
+ // fragment has the correct animation, so it is time to clear it.
+ View container = mContainer.onFindViewById(fragment.mContainerId);
+ if (container != null
+ && container.getTag(R.id.visible_removing_fragment_view_tag) != null) {
+ container.setTag(R.id.visible_removing_fragment_view_tag, null);
+ }
// If there is a transition on the container, clear those set on the fragment
if (fragment.mContainer != null && fragment.mContainer.getLayoutTransition() != null) {
return null;
@@ -1633,12 +1643,8 @@
* FragmentContainerView.
*/
void setExitAnimationOrder(Fragment f, boolean isPop) {
- // This will be false if a child fragment is added to its parent's childFragmentManager
- // before a view is created for Parent. In all other cases (adding a fragment to an
- // FragmentActivity's fragmentManager, adding a child fragment to a parent that has a view),
- // it should be true.
- if (mContainer.onHasView()) {
- ViewGroup container = (ViewGroup) mContainer.onFindViewById(f.mFragmentId);
+ ViewGroup container = getFragmentContainer(f);
+ if (container != null) {
if (container instanceof FragmentContainerView) {
((FragmentContainerView) container).setDrawDisappearingViewsLast(!isPop);
}
@@ -1923,6 +1929,7 @@
}
fragment.mAdded = false;
fragment.mRemoving = true;
+ setVisibleRemovingFragment(fragment);
}
}
@@ -1939,6 +1946,7 @@
// Toggle hidden changed so that if a fragment goes through show/hide/show
// it doesn't go through the animation.
fragment.mHiddenChanged = !fragment.mHiddenChanged;
+ setVisibleRemovingFragment(fragment);
}
}
@@ -1972,6 +1980,7 @@
mNeedMenuInvalidate = true;
}
fragment.mAdded = false;
+ setVisibleRemovingFragment(fragment);
}
}
}
@@ -2578,6 +2587,34 @@
}
/**
+ * Set a Fragment that is visibly being removed from the screen to a tag on its container.
+ * If a Fragment with the same container is already set, the previously added
+ * Fragment has its exit animation updated to the correct exit animation (either exit or
+ * pop_exit).
+ */
+ private void setVisibleRemovingFragment(Fragment f) {
+ ViewGroup container = getFragmentContainer(f);
+ if (container != null) {
+ if (container.getTag(R.id.visible_removing_fragment_view_tag) == null) {
+ container.setTag(R.id.visible_removing_fragment_view_tag, f);
+ }
+ ((Fragment) container.getTag(R.id.visible_removing_fragment_view_tag))
+ .setNextAnim(f.getNextAnim());
+ }
+ }
+
+ private ViewGroup getFragmentContainer(Fragment f) {
+ // This will be false if a child fragment is added to its parent's childFragmentManager
+ // before a view is created for Parent. In all other cases (adding a fragment to an
+ // FragmentActivity's fragmentManager, adding a child fragment to a parent that has a view),
+ // it should be true.
+ if (mContainer.onHasView()) {
+ return (ViewGroup) mContainer.onFindViewById(f.mContainerId);
+ }
+ return null;
+ }
+
+ /**
* Ensure that fragments that are added are moved to at least the CREATED state.
* Any newly-added Views are inserted into {@code added} so that the Transaction can be
* postponed with {@link Fragment#postponeEnterTransition()}. They will later be made
diff --git a/fragment/fragment/src/main/res/values/ids.xml b/fragment/fragment/src/main/res/values/ids.xml
index db4ce6e..7dad6a7 100644
--- a/fragment/fragment/src/main/res/values/ids.xml
+++ b/fragment/fragment/src/main/res/values/ids.xml
@@ -17,4 +17,5 @@
<resources>
<item type="id" name="fragment_container_view_tag" />
+ <item type="id" name="visible_removing_fragment_view_tag" />
</resources>
\ No newline at end of file