ViewPager hiding content too early when replaced [duplicate] - android
Here's the scenario: Activity contains fragment A, which in turn uses getChildFragmentManager() to add fragments A1 and A2 in its onCreate like so:
getChildFragmentManager()
.beginTransaction()
.replace(R.id.fragmentOneHolder, new FragmentA1())
.replace(R.id.fragmentTwoHolder, new FragmentA2())
.commit()
So far, so good, everything is running as expected.
We then run the following transaction in the Activity:
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(anim1, anim2, anim1, anim2)
.replace(R.id.fragmentHolder, new FragmentB())
.addToBackStack(null)
.commit()
During the transition, the enter animations for fragment B runs correctly but fragments A1 and A2 disappear entirely. When we revert the transaction with the Back button, they initialize properly and display normally during the popEnter animation.
In my brief testing, it got weirder - if I set the animations for the child fragments (see below), the exit animation runs intermittently when we add fragment B
getChildFragmentManager()
.beginTransaction()
.setCustomAnimations(enter, exit)
.replace(R.id.fragmentOneHolder, new FragmentA1())
.replace(R.id.fragmentTwoHolder, new FragmentA2())
.commit()
The effect I want to achieve is simple - I want the exit (or should it be popExit?) animation on fragment A (anim2) to run, animating the whole container, including its nested children.
Is there any way to achieve that?
Edit: Please find a test case here
Edit2: Thanks to #StevenByle for pushing me to keep trying with the static animations. Apparently you can set animations on a per-op basis (not global to the whole transaction), which means the children can have an indefinite static animation set, while their parent can have a different animation and the whole thing can be committed in one transaction. See the discussion below and the updated test case project.
So there seem to be a lot of different workarounds for this, but based on #Jayd16's answer, I think I've found a pretty solid catch-all solution that still allows for custom transition animations on child fragments, and doesn't require doing a bitmap cache of the layout.
Have a BaseFragment class that extends Fragment, and make all of your fragments extend that class (not just child fragments).
In that BaseFragment class, add the following:
// Arbitrary value; set it to some reasonable default
private static final int DEFAULT_CHILD_ANIMATION_DURATION = 250;
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
final Fragment parent = getParentFragment();
// Apply the workaround only if this is a child fragment, and the parent
// is being removed.
if (!enter && parent != null && parent.isRemoving()) {
// This is a workaround for the bug where child fragments disappear when
// the parent is removed (as all children are first removed from the parent)
// See https://code.google.com/p/android/issues/detail?id=55228
Animation doNothingAnim = new AlphaAnimation(1, 1);
doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
return doNothingAnim;
} else {
return super.onCreateAnimation(transit, enter, nextAnim);
}
}
private static long getNextAnimationDuration(Fragment fragment, long defValue) {
try {
// Attempt to get the resource ID of the next animation that
// will be applied to the given fragment.
Field nextAnimField = Fragment.class.getDeclaredField("mNextAnim");
nextAnimField.setAccessible(true);
int nextAnimResource = nextAnimField.getInt(fragment);
Animation nextAnim = AnimationUtils.loadAnimation(fragment.getActivity(), nextAnimResource);
// ...and if it can be loaded, return that animation's duration
return (nextAnim == null) ? defValue : nextAnim.getDuration();
} catch (NoSuchFieldException|IllegalAccessException|Resources.NotFoundException ex) {
Log.w(TAG, "Unable to load next animation from parent.", ex);
return defValue;
}
}
It does, unfortunately, require reflection; however, since this workaround is for the support library, you don't run the risk of the underlying implementation changing unless you update your support library. If you're building the support library from source, you could add an accessor for the next animation resource ID to Fragment.java and remove the need for reflection.
This solution removes the need to "guess" the parent's animation duration (so that the "do nothing" animation will have the same duration as the parent's exit animation), and allows you to still do custom animations on child fragments (e.g. if you're swapping child fragments around with different animations).
In order to avoid the user seeing the nested fragments disappearing when the parent fragment is removed/replaced in a transaction you could "simulate" those fragments still being present by providing an image of them, as they appeared on the screen. This image will be used as a background for the nested fragments container so even if the views of the nested fragment go away the image will simulate their presence. Also, I don't see loosing the interactivity with the nested fragment's views as a problem because I don't think you would want the user to act on them when they are just in the process of being removed(probably as a user action as well).
I've made a little example with setting up the background image(something basic).
I was able to come up with a pretty clean solution. IMO its the least hacky, and while this is technically the "draw a bitmap" solution at least its abstracted by the fragment lib.
Make sure your child frags override a parent class with this:
private static final Animation dummyAnimation = new AlphaAnimation(1,1);
static{
dummyAnimation.setDuration(500);
}
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if(!enter && getParentFragment() != null){
return dummyAnimation;
}
return super.onCreateAnimation(transit, enter, nextAnim);
}
If we have an exit animation on the child frags, they will be animated instead of blink away. We can exploit this by having an animation that simply draws the child fragments at full alpha for a duration. This way, they'll stay visible in the parent fragment as it animates, giving the desired behavior.
The only issue I can think of is keeping track of that duration. I could maybe set it to a large-ish number but I'm afraid that might have performance issues if its still drawing that animation somewhere.
Im posting my solution for clarity.
The solution is quite simple. If you are trying to mimic the parent's fragment transaction animation just simply add a custom animation to the child fragment transaction with same duration. Oh and make sure you set the custom animation before add().
getChildFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none)
.add(R.id.container, nestedFragment)
.commit();
The xml for R.anim.none (My parents enter/exit animation time is 250ms)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="0" android:duration="250" />
</set>
I understand this may not be able to completely solve your problem, but maybe it will suit someone else's needs, you can add enter/exit and popEnter/popExit animations to your children Fragments that do not actually move/animate the Fragments. As long as the animations have the same duration/offset as their parent Fragment animations, they will appear to move/animate with the parent's animation.
you can do this in the child fragment.
#Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
if (true) {//condition
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(getView(), "alpha", 1, 1);
objectAnimator.setDuration(333);//time same with parent fragment's animation
return objectAnimator;
}
return super.onCreateAnimator(transit, enter, nextAnim);
}
############################
EDIT:
I ended up unimplementing this solution as there were other problems that this has. Square recently came out with 2 libraries that replace fragments. I'd say this might actually be a better alternative than trying to hack fragments into doing something google doesn't want them doing.
http://corner.squareup.com/2014/01/mortar-and-flow.html
############################
I figured I'd put up this solution to help people who have this problem in the future. If you trace through the original posters conversation with other people, and look at the code he posted, you'll see the original poster eventually comes to the conclusion of using a no-op animation on the child fragments while animating the parent fragment. This solution isn't ideal as it forces you to keep track of all the child fragments, which can be cumbersome when using a ViewPager with FragmentPagerAdapter.
Since I use Child Fragments all over the place I came up with this solution that is efficient, and modular (so it can be easily removed) in case they ever fix it and this no-op animation is no longer needed.
There are lots of ways you can implement this. I chose to use a singleton, and I call it ChildFragmentAnimationManager. It basically will keep track of a child fragment for me based on its parent and will apply a no-op animation to the children when asked.
public class ChildFragmentAnimationManager {
private static ChildFragmentAnimationManager instance = null;
private Map<Fragment, List<Fragment>> fragmentMap;
private ChildFragmentAnimationManager() {
fragmentMap = new HashMap<Fragment, List<Fragment>>();
}
public static ChildFragmentAnimationManager instance() {
if (instance == null) {
instance = new ChildFragmentAnimationManager();
}
return instance;
}
public FragmentTransaction animate(FragmentTransaction ft, Fragment parent) {
List<Fragment> children = getChildren(parent);
ft.setCustomAnimations(R.anim.no_anim, R.anim.no_anim, R.anim.no_anim, R.anim.no_anim);
for (Fragment child : children) {
ft.remove(child);
}
return ft;
}
public void putChild(Fragment parent, Fragment child) {
List<Fragment> children = getChildren(parent);
children.add(child);
}
public void removeChild(Fragment parent, Fragment child) {
List<Fragment> children = getChildren(parent);
children.remove(child);
}
private List<Fragment> getChildren(Fragment parent) {
List<Fragment> children;
if ( fragmentMap.containsKey(parent) ) {
children = fragmentMap.get(parent);
} else {
children = new ArrayList<Fragment>(3);
fragmentMap.put(parent, children);
}
return children;
}
}
Next you need to have a class that extends Fragment that all your Fragments extend (at least your Child Fragments). I already had this class, and I call it BaseFragment. When a fragments view is created, we add it to the ChildFragmentAnimationManager, and remove it when it's destroyed. You could do this onAttach/Detach, or other matching methods in the sequence. My logic for choosing Create/Destroy View was because if a Fragment doesn't have a View, I don't care about animating it to continue to be seen. This approach should also work better with ViewPagers that use Fragments as you won't be keeping track of every single Fragment that a FragmentPagerAdapter is holding, but rather only 3.
public abstract class BaseFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Fragment parent = getParentFragment();
if (parent != null) {
ChildFragmentAnimationManager.instance().putChild(parent, this);
}
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onDestroyView() {
Fragment parent = getParentFragment();
if (parent != null) {
ChildFragmentAnimationManager.instance().removeChild(parent, this);
}
super.onDestroyView();
}
}
Now that all your Fragments are stored in memory by the parent fragment, you can call animate on them like this, and your child fragments won't disappear.
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ChildFragmentAnimationManager.instance().animate(ft, ReaderFragment.this)
.setCustomAnimations(R.anim.up_in, R.anim.up_out, R.anim.down_in, R.anim.down_out)
.replace(R.id.container, f)
.addToBackStack(null)
.commit();
Also, just so you have it, here is the no_anim.xml file that goes in your res/anim folder:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="#android:anim/linear_interpolator">
<translate android:fromXDelta="0" android:toXDelta="0"
android:duration="1000" />
</set>
Again, I don't think this solution is perfect, but it's much better than for every instance you have a Child Fragment, implementing custom code in the parent fragment to keep track of each child. I've been there, and it's no fun.
I was having the same issue with map fragment. It kept disappearing during the exit animation of its containing fragment. The workaround is to add animation for the child map fragment which will keep it visible during the exit animation of the parent fragment. The animation of the child fragment is keeping its alpha at 100% during its duration period.
Animation: res/animator/keep_child_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="alpha"
android:valueFrom="1.0"
android:valueTo="1.0"
android:duration="#integer/keep_child_fragment_animation_duration" />
</set>
The animation is then applied when the map fragment is added to the parent fragment.
Parent fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.map_parent_fragment, container, false);
MapFragment mapFragment = MapFragment.newInstance();
getChildFragmentManager().beginTransaction()
.setCustomAnimations(R.animator.keep_child_fragment, 0, 0, 0)
.add(R.id.map, mapFragment)
.commit();
return view;
}
Finally, the duration of the child fragment animation is set in a resource file.
values/integers.xml
<resources>
<integer name="keep_child_fragment_animation_duration">500</integer>
</resources>
I think i found a better solution to this problem than snapshotting the current fragment to a bitmap as Luksprog suggested.
The trick is to hide the fragment being removed or detached and only after the animations have been completed the fragment is removed or detached in its own fragment transaction.
Imagine we have FragmentA and FragmentB, both with sub fragments. Now when you would normally do:
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(anim1, anim2, anim1, anim2)
.add(R.id.fragmentHolder, new FragmentB())
.remove(fragmentA) <-------------------------------------------
.addToBackStack(null)
.commit()
Instead you do
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(anim1, anim2, anim1, anim2)
.add(R.id.fragmentHolder, new FragmentB())
.hide(fragmentA) <---------------------------------------------
.addToBackStack(null)
.commit()
fragmentA.removeMe = true;
Now for the implementation of the Fragment:
public class BaseFragment extends Fragment {
protected Boolean detachMe = false;
protected Boolean removeMe = false;
#Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if (nextAnim == 0) {
if (!enter) {
onExit();
}
return null;
}
Animation animation = AnimationUtils.loadAnimation(getActivity(), nextAnim);
assert animation != null;
if (!enter) {
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
onExit();
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
}
return animation;
}
private void onExit() {
if (!detachMe && !removeMe) {
return;
}
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
if (detachMe) {
fragmentTransaction.detach(this);
detachMe = false;
} else if (removeMe) {
fragmentTransaction.remove(this);
removeMe = false;
}
fragmentTransaction.commit();
}
}
To animate dissapearance of neasted fragments we can force pop back stack on ChildFragmentManager. This will fire transition animation. To do this we need to catch up OnBackButtonPressed event or listen for backstack changes.
Here is example with code.
View.OnClickListener() {//this is from custom button but you can listen for back button pressed
#Override
public void onClick(View v) {
getChildFragmentManager().popBackStack();
//and here we can manage other fragment operations
}
});
Fragment fr = MyNeastedFragment.newInstance(product);
getChildFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
.replace(R.neasted_fragment_container, fr)
.addToBackStack("Neasted Fragment")
.commit();
I recently ran into this problem in my question: Nested fragments transitioning incorrectly
I have a solution that solves this without saving a bitmap, nor using reflection or any other unsatisfying methods.
An example project can be viewed here: https://github.com/zafrani/NestedFragmentTransitions
A GIF of the effect can be viewed here: https://imgur.com/94AvrW4
In my example there are 6 children fragments, split between two parent fragments. I'm able to achieve the transitions for enter, exit, pop and push without any problems. Configuration changes and back presses are also successfully handled.
The bulk of the solution is in my BaseFragment's (the fragment extended by my children and parent fragments) onCreateAnimator function which looks like this:
override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator {
if (isConfigChange) {
resetStates()
return nothingAnim()
}
if (parentFragment is ParentFragment) {
if ((parentFragment as BaseFragment).isPopping) {
return nothingAnim()
}
}
if (parentFragment != null && parentFragment.isRemoving) {
return nothingAnim()
}
if (enter) {
if (isPopping) {
resetStates()
return pushAnim()
}
if (isSuppressing) {
resetStates()
return nothingAnim()
}
return enterAnim()
}
if (isPopping) {
resetStates()
return popAnim()
}
if (isSuppressing) {
resetStates()
return nothingAnim()
}
return exitAnim()
}
The activity and parent fragment are responsible for setting the states of these booleans. Its easier to view how and where from my example project.
I am not using support fragments in my example, but the same logic can be used with them and their onCreateAnimation function
A simple way to fix this problem is use the Fragment class from this library instead of the standard library fragment class:
https://github.com/marksalpeter/contract-fragment
As a side note, the package also contains a useful delegate pattern called ContractFragment that you might find useful for building your apps leveraging the parent-child fragment relationship.
From the above answer of #kcoppock,
if you have Activity->Fragment->Fragments ( multiple stacking, the following helps ), a minor edit to the best answer IMHO.
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
final Fragment parent = getParentFragment();
Fragment parentOfParent = null;
if( parent!=null ) {
parentOfParent = parent.getParentFragment();
}
if( !enter && parent != null && parentOfParent!=null && parentOfParent.isRemoving()){
Animation doNothingAnim = new AlphaAnimation(1, 1);
doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
return doNothingAnim;
} else
if (!enter && parent != null && parent.isRemoving()) {
// This is a workaround for the bug where child fragments disappear when
// the parent is removed (as all children are first removed from the parent)
// See https://code.google.com/p/android/issues/detail?id=55228
Animation doNothingAnim = new AlphaAnimation(1, 1);
doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
return doNothingAnim;
} else {
return super.onCreateAnimation(transit, enter, nextAnim);
}
}
My problem was on parent fragment removal (ft.remove(fragment)), child animations were not happening.
The basic problem is that child fragments are immediately DESTROYED PRIOR to the parents fragment exiting animation.
Child fragments custom animations do not get executed on Parent Fragment removal
As others have eluded to, hiding the PARENT (and not the child) prior to PARENT removal is the way to go.
val ft = fragmentManager?.beginTransaction()
ft?.setCustomAnimations(R.anim.enter_from_right,
R.anim.exit_to_right)
if (parentFragment.isHidden()) {
ft?.show(vehicleModule)
} else {
ft?.hide(vehicleModule)
}
ft?.commit()
If you actually want to remove the parent you should probably set up a listener on you custom animation to know when the animation is ended, so then you can safely do some finalisation on the Parent Fragment (remove). If you don't do this, in a timely fashion, you could end up killing the animation. N.B animation is done on asynchronous queue of its own.
BTW you don't need custom animations on the child fragment, as they will inherit the parent animations.
The issue is fixed in androidx.fragment:fragment:1.2.0-alpha02. See https://issuetracker.google.com/issues/116675313 for more details.
Old thread, but in case someone stumbles in here:
All of the approaches above feel very unappealing for me, the bitmap solution is very dirty and non performant; the other ones require the child fragments to know about the duration of the transition used in the transaction used to create the child fragment in question. A better solution in my eyes i something like the following:
val currentFragment = supportFragmentManager.findFragmentByTag(TAG)
val transaction = supportFragmentManager
.beginTransaction()
.setCustomAnimations(anim1, anim2, anim1, anim2)
.add(R.id.fragmentHolder, FragmentB(), TAG)
if (currentFragment != null) {
transaction.hide(currentFragment).commit()
Handler().postDelayed({
supportFragmentManager.beginTransaction().remove(currentFragment).commit()
}, DURATION_OF_ANIM)
} else {
transaction.commit()
}
We just hide the current fragment and add the new fragment, when the animation has finished we remove the old fragment. This way it is handled in one place and no bitmap is created.
Related
Exit transition does not work for an ImageView Shared Element in a ViewPager in a Fragment
In my app, I have an Activity launching another Activity with a Fragment in it, that contains a ViewPager of images. What I have working currently, is the enter transition where the first Activity launches the second and the transition is correct. This works because, in my ViewPager I put a OnPreDrawListener on it and only resume the activity transition when the image in the pager is loaded. It looks like this: public class ImagePagerAdapter extends PagerAdapter { // Constructor and other things.. #Override public Object instantiateItem(ViewGroup container, final int position) { ImageView imageView; if (position == 0) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Shared element is the first one. ViewCompat.setTransitionName(imageView, "sharedImage"); } } imageView = new ImageView(activity); // Just a reusable static Helper class. HelperPicasso.loadImage(images.get(position), imageView, false, new Callback() { #Override public void onSuccess() { imageView.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { #Override public boolean onPreDraw() { imageView.getViewTreeObserver() .removeOnPreDrawListener(this); // When the ImageView is ready to be drawn, we can continue our activity/fragment's postponed transition animation. // Why? Because we want have the first image be the shared element, and we can only set it after instantiation. ActivityCompat.startPostponedEnterTransition(activity); return true; } }); } #Override public void onError() { ActivityCompat.startPostponedEnterTransition(activity); } }); } } Aside from the ImageView, I also have a FrameLayout which is a shared element as well, but I mark it with it's transition name in the onCreateView of the fragment. With this the enter transition works well for me. However, when I press the back button, the FrameLayout's exit transitions works correctly, but the ViewPager image goes blank. My guess is that the fragment's lifecycle causes the ViewPager (and it's child views) to be destroyed during the exit transition. I've tried adding ActivityCompat.finishAfterTransition(this) in the onBackPressed callback for the parent Activity, but it doesn't seem to have any effect.
When you have a ViewPager in the middle of a transition then you have to do some extra work in order to have a fluid and "beautiful" transition. You must play with postponeTransition() and startPostponedTransition() in order to play the transitions only when fragments or images finished to load. (It seems that you are already doing it). I recommend you to you to check the next blog: Shared Element Transitions - Part 4: RecyclerView The target of that article is more RecyclerView + ViewAdapter + Fragments transitions but I´m sure you can adapt it in your scenario. Hope it helps.
Adding function to addToBackStack()
I'm using an OnClickListener to do a fragment replacement. I'm toggling 3 LinearLayouts to 'GONE' within the OnClickListener also. I would like to add a function to set the 3 LinearLayouts back to VISIBLE when the back button is pressed. The fragments swap back, but the LinearLayouts don't change their state. Any help is appreciated, thanks! final OnClickListener swapFragments = new OnClickListener() { #Override public void onClick(View v) { if (myAdapter.isEmpty() != true) { FragmentTransaction ft = getFragmentManager() .beginTransaction(); FragmentTwoTop ftt = new FragmentTwoTop(); FragmentTwoBottom ftb = new FragmentTwoBottom(); ft.replace(R.id.leftTopHolder, ftt, "fragmenttwotop"); ft.replace(R.id.leftBottomtHolder, ftb, "fragmenttwobottom"); layoutOne.setVisibility(View.GONE); layoutTwo.setVisibility(View.GONE); layoutThree.setVisibility(View.GONE); ft.addToBackStack("swapfragments"); ft.commit(); } else { } } };
You could try adding a listener to the backstack: http://developer.android.com/reference/android/app/FragmentManager.html#addOnBackStackChangedListener(android.app.FragmentManager.OnBackStackChangedListener) It will be called any time "something" is added or removed from/to the backstack. You can then check the type of the class of the fragment (or maybe your holding the current fragment in a class - your Activity - variable) to decide if it's necessary to execute your animation. The method in which you manage the layouts could simply check the visibility (getVisibility) and if it's VISIBLE then set to GONE, if it's GONE set to VISIBLE.
Nested fragments disappear during transition animation
Here's the scenario: Activity contains fragment A, which in turn uses getChildFragmentManager() to add fragments A1 and A2 in its onCreate like so: getChildFragmentManager() .beginTransaction() .replace(R.id.fragmentOneHolder, new FragmentA1()) .replace(R.id.fragmentTwoHolder, new FragmentA2()) .commit() So far, so good, everything is running as expected. We then run the following transaction in the Activity: getSupportFragmentManager() .beginTransaction() .setCustomAnimations(anim1, anim2, anim1, anim2) .replace(R.id.fragmentHolder, new FragmentB()) .addToBackStack(null) .commit() During the transition, the enter animations for fragment B runs correctly but fragments A1 and A2 disappear entirely. When we revert the transaction with the Back button, they initialize properly and display normally during the popEnter animation. In my brief testing, it got weirder - if I set the animations for the child fragments (see below), the exit animation runs intermittently when we add fragment B getChildFragmentManager() .beginTransaction() .setCustomAnimations(enter, exit) .replace(R.id.fragmentOneHolder, new FragmentA1()) .replace(R.id.fragmentTwoHolder, new FragmentA2()) .commit() The effect I want to achieve is simple - I want the exit (or should it be popExit?) animation on fragment A (anim2) to run, animating the whole container, including its nested children. Is there any way to achieve that? Edit: Please find a test case here Edit2: Thanks to #StevenByle for pushing me to keep trying with the static animations. Apparently you can set animations on a per-op basis (not global to the whole transaction), which means the children can have an indefinite static animation set, while their parent can have a different animation and the whole thing can be committed in one transaction. See the discussion below and the updated test case project.
So there seem to be a lot of different workarounds for this, but based on #Jayd16's answer, I think I've found a pretty solid catch-all solution that still allows for custom transition animations on child fragments, and doesn't require doing a bitmap cache of the layout. Have a BaseFragment class that extends Fragment, and make all of your fragments extend that class (not just child fragments). In that BaseFragment class, add the following: // Arbitrary value; set it to some reasonable default private static final int DEFAULT_CHILD_ANIMATION_DURATION = 250; #Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { final Fragment parent = getParentFragment(); // Apply the workaround only if this is a child fragment, and the parent // is being removed. if (!enter && parent != null && parent.isRemoving()) { // This is a workaround for the bug where child fragments disappear when // the parent is removed (as all children are first removed from the parent) // See https://code.google.com/p/android/issues/detail?id=55228 Animation doNothingAnim = new AlphaAnimation(1, 1); doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION)); return doNothingAnim; } else { return super.onCreateAnimation(transit, enter, nextAnim); } } private static long getNextAnimationDuration(Fragment fragment, long defValue) { try { // Attempt to get the resource ID of the next animation that // will be applied to the given fragment. Field nextAnimField = Fragment.class.getDeclaredField("mNextAnim"); nextAnimField.setAccessible(true); int nextAnimResource = nextAnimField.getInt(fragment); Animation nextAnim = AnimationUtils.loadAnimation(fragment.getActivity(), nextAnimResource); // ...and if it can be loaded, return that animation's duration return (nextAnim == null) ? defValue : nextAnim.getDuration(); } catch (NoSuchFieldException|IllegalAccessException|Resources.NotFoundException ex) { Log.w(TAG, "Unable to load next animation from parent.", ex); return defValue; } } It does, unfortunately, require reflection; however, since this workaround is for the support library, you don't run the risk of the underlying implementation changing unless you update your support library. If you're building the support library from source, you could add an accessor for the next animation resource ID to Fragment.java and remove the need for reflection. This solution removes the need to "guess" the parent's animation duration (so that the "do nothing" animation will have the same duration as the parent's exit animation), and allows you to still do custom animations on child fragments (e.g. if you're swapping child fragments around with different animations).
In order to avoid the user seeing the nested fragments disappearing when the parent fragment is removed/replaced in a transaction you could "simulate" those fragments still being present by providing an image of them, as they appeared on the screen. This image will be used as a background for the nested fragments container so even if the views of the nested fragment go away the image will simulate their presence. Also, I don't see loosing the interactivity with the nested fragment's views as a problem because I don't think you would want the user to act on them when they are just in the process of being removed(probably as a user action as well). I've made a little example with setting up the background image(something basic).
I was able to come up with a pretty clean solution. IMO its the least hacky, and while this is technically the "draw a bitmap" solution at least its abstracted by the fragment lib. Make sure your child frags override a parent class with this: private static final Animation dummyAnimation = new AlphaAnimation(1,1); static{ dummyAnimation.setDuration(500); } #Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { if(!enter && getParentFragment() != null){ return dummyAnimation; } return super.onCreateAnimation(transit, enter, nextAnim); } If we have an exit animation on the child frags, they will be animated instead of blink away. We can exploit this by having an animation that simply draws the child fragments at full alpha for a duration. This way, they'll stay visible in the parent fragment as it animates, giving the desired behavior. The only issue I can think of is keeping track of that duration. I could maybe set it to a large-ish number but I'm afraid that might have performance issues if its still drawing that animation somewhere.
Im posting my solution for clarity. The solution is quite simple. If you are trying to mimic the parent's fragment transaction animation just simply add a custom animation to the child fragment transaction with same duration. Oh and make sure you set the custom animation before add(). getChildFragmentManager().beginTransaction() .setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none) .add(R.id.container, nestedFragment) .commit(); The xml for R.anim.none (My parents enter/exit animation time is 250ms) <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="0" android:toXDelta="0" android:duration="250" /> </set>
I understand this may not be able to completely solve your problem, but maybe it will suit someone else's needs, you can add enter/exit and popEnter/popExit animations to your children Fragments that do not actually move/animate the Fragments. As long as the animations have the same duration/offset as their parent Fragment animations, they will appear to move/animate with the parent's animation.
you can do this in the child fragment. #Override public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) { if (true) {//condition ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(getView(), "alpha", 1, 1); objectAnimator.setDuration(333);//time same with parent fragment's animation return objectAnimator; } return super.onCreateAnimator(transit, enter, nextAnim); }
############################ EDIT: I ended up unimplementing this solution as there were other problems that this has. Square recently came out with 2 libraries that replace fragments. I'd say this might actually be a better alternative than trying to hack fragments into doing something google doesn't want them doing. http://corner.squareup.com/2014/01/mortar-and-flow.html ############################ I figured I'd put up this solution to help people who have this problem in the future. If you trace through the original posters conversation with other people, and look at the code he posted, you'll see the original poster eventually comes to the conclusion of using a no-op animation on the child fragments while animating the parent fragment. This solution isn't ideal as it forces you to keep track of all the child fragments, which can be cumbersome when using a ViewPager with FragmentPagerAdapter. Since I use Child Fragments all over the place I came up with this solution that is efficient, and modular (so it can be easily removed) in case they ever fix it and this no-op animation is no longer needed. There are lots of ways you can implement this. I chose to use a singleton, and I call it ChildFragmentAnimationManager. It basically will keep track of a child fragment for me based on its parent and will apply a no-op animation to the children when asked. public class ChildFragmentAnimationManager { private static ChildFragmentAnimationManager instance = null; private Map<Fragment, List<Fragment>> fragmentMap; private ChildFragmentAnimationManager() { fragmentMap = new HashMap<Fragment, List<Fragment>>(); } public static ChildFragmentAnimationManager instance() { if (instance == null) { instance = new ChildFragmentAnimationManager(); } return instance; } public FragmentTransaction animate(FragmentTransaction ft, Fragment parent) { List<Fragment> children = getChildren(parent); ft.setCustomAnimations(R.anim.no_anim, R.anim.no_anim, R.anim.no_anim, R.anim.no_anim); for (Fragment child : children) { ft.remove(child); } return ft; } public void putChild(Fragment parent, Fragment child) { List<Fragment> children = getChildren(parent); children.add(child); } public void removeChild(Fragment parent, Fragment child) { List<Fragment> children = getChildren(parent); children.remove(child); } private List<Fragment> getChildren(Fragment parent) { List<Fragment> children; if ( fragmentMap.containsKey(parent) ) { children = fragmentMap.get(parent); } else { children = new ArrayList<Fragment>(3); fragmentMap.put(parent, children); } return children; } } Next you need to have a class that extends Fragment that all your Fragments extend (at least your Child Fragments). I already had this class, and I call it BaseFragment. When a fragments view is created, we add it to the ChildFragmentAnimationManager, and remove it when it's destroyed. You could do this onAttach/Detach, or other matching methods in the sequence. My logic for choosing Create/Destroy View was because if a Fragment doesn't have a View, I don't care about animating it to continue to be seen. This approach should also work better with ViewPagers that use Fragments as you won't be keeping track of every single Fragment that a FragmentPagerAdapter is holding, but rather only 3. public abstract class BaseFragment extends Fragment { #Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Fragment parent = getParentFragment(); if (parent != null) { ChildFragmentAnimationManager.instance().putChild(parent, this); } return super.onCreateView(inflater, container, savedInstanceState); } #Override public void onDestroyView() { Fragment parent = getParentFragment(); if (parent != null) { ChildFragmentAnimationManager.instance().removeChild(parent, this); } super.onDestroyView(); } } Now that all your Fragments are stored in memory by the parent fragment, you can call animate on them like this, and your child fragments won't disappear. FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction(); ChildFragmentAnimationManager.instance().animate(ft, ReaderFragment.this) .setCustomAnimations(R.anim.up_in, R.anim.up_out, R.anim.down_in, R.anim.down_out) .replace(R.id.container, f) .addToBackStack(null) .commit(); Also, just so you have it, here is the no_anim.xml file that goes in your res/anim folder: <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="#android:anim/linear_interpolator"> <translate android:fromXDelta="0" android:toXDelta="0" android:duration="1000" /> </set> Again, I don't think this solution is perfect, but it's much better than for every instance you have a Child Fragment, implementing custom code in the parent fragment to keep track of each child. I've been there, and it's no fun.
I was having the same issue with map fragment. It kept disappearing during the exit animation of its containing fragment. The workaround is to add animation for the child map fragment which will keep it visible during the exit animation of the parent fragment. The animation of the child fragment is keeping its alpha at 100% during its duration period. Animation: res/animator/keep_child_fragment.xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:propertyName="alpha" android:valueFrom="1.0" android:valueTo="1.0" android:duration="#integer/keep_child_fragment_animation_duration" /> </set> The animation is then applied when the map fragment is added to the parent fragment. Parent fragment #Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.map_parent_fragment, container, false); MapFragment mapFragment = MapFragment.newInstance(); getChildFragmentManager().beginTransaction() .setCustomAnimations(R.animator.keep_child_fragment, 0, 0, 0) .add(R.id.map, mapFragment) .commit(); return view; } Finally, the duration of the child fragment animation is set in a resource file. values/integers.xml <resources> <integer name="keep_child_fragment_animation_duration">500</integer> </resources>
I think i found a better solution to this problem than snapshotting the current fragment to a bitmap as Luksprog suggested. The trick is to hide the fragment being removed or detached and only after the animations have been completed the fragment is removed or detached in its own fragment transaction. Imagine we have FragmentA and FragmentB, both with sub fragments. Now when you would normally do: getSupportFragmentManager() .beginTransaction() .setCustomAnimations(anim1, anim2, anim1, anim2) .add(R.id.fragmentHolder, new FragmentB()) .remove(fragmentA) <------------------------------------------- .addToBackStack(null) .commit() Instead you do getSupportFragmentManager() .beginTransaction() .setCustomAnimations(anim1, anim2, anim1, anim2) .add(R.id.fragmentHolder, new FragmentB()) .hide(fragmentA) <--------------------------------------------- .addToBackStack(null) .commit() fragmentA.removeMe = true; Now for the implementation of the Fragment: public class BaseFragment extends Fragment { protected Boolean detachMe = false; protected Boolean removeMe = false; #Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { if (nextAnim == 0) { if (!enter) { onExit(); } return null; } Animation animation = AnimationUtils.loadAnimation(getActivity(), nextAnim); assert animation != null; if (!enter) { animation.setAnimationListener(new Animation.AnimationListener() { #Override public void onAnimationStart(Animation animation) { } #Override public void onAnimationEnd(Animation animation) { onExit(); } #Override public void onAnimationRepeat(Animation animation) { } }); } return animation; } private void onExit() { if (!detachMe && !removeMe) { return; } FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); if (detachMe) { fragmentTransaction.detach(this); detachMe = false; } else if (removeMe) { fragmentTransaction.remove(this); removeMe = false; } fragmentTransaction.commit(); } }
To animate dissapearance of neasted fragments we can force pop back stack on ChildFragmentManager. This will fire transition animation. To do this we need to catch up OnBackButtonPressed event or listen for backstack changes. Here is example with code. View.OnClickListener() {//this is from custom button but you can listen for back button pressed #Override public void onClick(View v) { getChildFragmentManager().popBackStack(); //and here we can manage other fragment operations } }); Fragment fr = MyNeastedFragment.newInstance(product); getChildFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) .replace(R.neasted_fragment_container, fr) .addToBackStack("Neasted Fragment") .commit();
I recently ran into this problem in my question: Nested fragments transitioning incorrectly I have a solution that solves this without saving a bitmap, nor using reflection or any other unsatisfying methods. An example project can be viewed here: https://github.com/zafrani/NestedFragmentTransitions A GIF of the effect can be viewed here: https://imgur.com/94AvrW4 In my example there are 6 children fragments, split between two parent fragments. I'm able to achieve the transitions for enter, exit, pop and push without any problems. Configuration changes and back presses are also successfully handled. The bulk of the solution is in my BaseFragment's (the fragment extended by my children and parent fragments) onCreateAnimator function which looks like this: override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator { if (isConfigChange) { resetStates() return nothingAnim() } if (parentFragment is ParentFragment) { if ((parentFragment as BaseFragment).isPopping) { return nothingAnim() } } if (parentFragment != null && parentFragment.isRemoving) { return nothingAnim() } if (enter) { if (isPopping) { resetStates() return pushAnim() } if (isSuppressing) { resetStates() return nothingAnim() } return enterAnim() } if (isPopping) { resetStates() return popAnim() } if (isSuppressing) { resetStates() return nothingAnim() } return exitAnim() } The activity and parent fragment are responsible for setting the states of these booleans. Its easier to view how and where from my example project. I am not using support fragments in my example, but the same logic can be used with them and their onCreateAnimation function
A simple way to fix this problem is use the Fragment class from this library instead of the standard library fragment class: https://github.com/marksalpeter/contract-fragment As a side note, the package also contains a useful delegate pattern called ContractFragment that you might find useful for building your apps leveraging the parent-child fragment relationship.
From the above answer of #kcoppock, if you have Activity->Fragment->Fragments ( multiple stacking, the following helps ), a minor edit to the best answer IMHO. public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { final Fragment parent = getParentFragment(); Fragment parentOfParent = null; if( parent!=null ) { parentOfParent = parent.getParentFragment(); } if( !enter && parent != null && parentOfParent!=null && parentOfParent.isRemoving()){ Animation doNothingAnim = new AlphaAnimation(1, 1); doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION)); return doNothingAnim; } else if (!enter && parent != null && parent.isRemoving()) { // This is a workaround for the bug where child fragments disappear when // the parent is removed (as all children are first removed from the parent) // See https://code.google.com/p/android/issues/detail?id=55228 Animation doNothingAnim = new AlphaAnimation(1, 1); doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION)); return doNothingAnim; } else { return super.onCreateAnimation(transit, enter, nextAnim); } }
My problem was on parent fragment removal (ft.remove(fragment)), child animations were not happening. The basic problem is that child fragments are immediately DESTROYED PRIOR to the parents fragment exiting animation. Child fragments custom animations do not get executed on Parent Fragment removal As others have eluded to, hiding the PARENT (and not the child) prior to PARENT removal is the way to go. val ft = fragmentManager?.beginTransaction() ft?.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_right) if (parentFragment.isHidden()) { ft?.show(vehicleModule) } else { ft?.hide(vehicleModule) } ft?.commit() If you actually want to remove the parent you should probably set up a listener on you custom animation to know when the animation is ended, so then you can safely do some finalisation on the Parent Fragment (remove). If you don't do this, in a timely fashion, you could end up killing the animation. N.B animation is done on asynchronous queue of its own. BTW you don't need custom animations on the child fragment, as they will inherit the parent animations.
The issue is fixed in androidx.fragment:fragment:1.2.0-alpha02. See https://issuetracker.google.com/issues/116675313 for more details.
Old thread, but in case someone stumbles in here: All of the approaches above feel very unappealing for me, the bitmap solution is very dirty and non performant; the other ones require the child fragments to know about the duration of the transition used in the transaction used to create the child fragment in question. A better solution in my eyes i something like the following: val currentFragment = supportFragmentManager.findFragmentByTag(TAG) val transaction = supportFragmentManager .beginTransaction() .setCustomAnimations(anim1, anim2, anim1, anim2) .add(R.id.fragmentHolder, FragmentB(), TAG) if (currentFragment != null) { transaction.hide(currentFragment).commit() Handler().postDelayed({ supportFragmentManager.beginTransaction().remove(currentFragment).commit() }, DURATION_OF_ANIM) } else { transaction.commit() } We just hide the current fragment and add the new fragment, when the animation has finished we remove the old fragment. This way it is handled in one place and no bitmap is created.
Android Fragment Animation onResume
Here is the situation: I am using child fragment introduced with API 17. Say I have ActivityA -> FragmentA ActivityA -> FragmentB FragmentA -> ChildFragmentA FragmentA -> ChildFragmentB So I'm on ActivityA->FragmentA->ChildFragmentA and I transition to ActivityA->FragmentA->ChildFragmentB with a FragmentTransaction using animations for adding to the backstack and popping the backstack (there is an animation when I go to ChildFragmentB and an animation when I press back and move to ChildFragmentA). Now I navigate to ActivityA->FragmentB (FragmentA is no long attached). When I navigate back to ActivityA->FragmentA where ChildFragmentB is visible, ChildFragmentB animates in as it did when coming from ChildFragmentA. I want to disable this animation when resuming Activity->FragmentA. But keep it when transitioning between the children fragments. This animation is set in the FragmentTransaction. Is there any way to make this happen?
I solved this by adding some logic to the parent fragment to detect when it is being hidden or shown, and explicitly disable or enable animations in its child. #Override public void onPause() { super.onPause(); // If this fragment is being closed/replaced then disable animations // in child fragments. Otherwise we get very nasty visual effects // with the parent and child animations running simultaneously ChildFragment f = (ChildFragment) getChildFragmentManager() .findFragmentByTag(FRAGMENT_CHILD); if (f != null) { f.disableAnimations(); } } #Override public void onResume() { super.onResume(); // if this fragment is being opened then re-enable animations // in child fragments ChildFragment f = (ChildFragment) getChildFragmentManager() .findFragmentByTag(FRAGMENT_CHILD); if (f != null) { f.enableAnimations(); } } In the child fragment, we need to implement those methods to enable/disable animations. We do this by overriding onCreateAnimation() and using a static animation (R.anim.hold) in the case where animations should be disabled. private boolean mDisableAnimations; void disableAnimations() { mDisableAnimations = true; } void enableAnimations() { mDisableAnimations = false; } #Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { if (mDisableAnimations) { return AnimationUtils.loadAnimation(getActivity(), R.anim.hold); } return super.onCreateAnimation(transit, enter, nextAnim); } The static animation is defined in res/anim/hold.xml as: <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:fromXDelta="0" android:toXDelta="0" android:duration="2000" />
Try to disable the transition onAttach in ActivityA->FragmentA and re-enable it again onResume in the ChildFragments of both FragmentA and B
Pop the fragment backstack without playing the Pop-Animation
I push a fragment on the fragment stack using the following code: FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right, R.anim.slide_in_left, R.anim.slide_out_left); fragmentTransaction.replace(getId(), newFragment); fragmentTransaction.addToBackStack(null); fragmentTransaction.commit(); This way, when the fragment stack is popped, e.g. by pressing the back button, a fragment pop animation is played. However, there are situations in which i would like to pop the fragment backstack without showing this animation, e.g. because I just returned from another activity and want to display the previous fragment at once, without animation. An example navigation could look like this: The user is on the start screen with the root fragment He selects an item on the root fragment which then displays a new fragment to show details of that item. It does so using a fragment transaction that sets animations both for the push and the pop case (so when the user presses the back button, the transition is animated) From this fragment he starts an activity which (for whatever reason) deletes the item that was just shown When this activity finishes, I would like to return to the root fragment without showing the "pop animation" of the "detail fragment" Is there a way to pop the fragment backstack without playing the specified pop animation?
So Warpzit was on the right track, he just didn't address your specific issue too well. I came across the exact same issue and here is how I solved it. First I created a static boolean variable (for simplicity's sake, lets put it in the FragmentUtils class)... public class FragmentUtils { public static boolean sDisableFragmentAnimations = false; } Then, in EVERY fragment you have, you need to override the onCreateAnimation method... #Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { if (FragmentUtils.sDisableFragmentAnimations) { Animation a = new Animation() {}; a.setDuration(0); return a; } return super.onCreateAnimation(transit, enter, nextAnim); } Then, when you need to clear the backstack from your activity simply do the following... public void clearBackStack() { FragmentUtils.sDisableFragmentAnimations = true; getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentUtils.sDisableFragmentAnimations = false; } And voila, a call to clearBackStack() will drop you back into the root fragment without any transition animations. Hopefully the big G will add a less stupid way of doing this in the future.
So for the support library following works: In the fragment which should have a custom pop animation you override the onCreateAnimation with your own custom one. You could get it and set some kind of parameter depending on what you want. There might need to be done some extra work to make it work with regular fragments. Here is the example where I'm overriding it and changing the set duration: #Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { Animation anim = (Animation) super.onCreateAnimation(transit, enter, nextAnim); if(!enter) { if(anim != null) { anim.setDuration(0); // This doesn't seem to be called. return anim; } else { Animation test = new TestAnimation(); test.setDuration(0); return test; } } return anim; } private class TestAnimation extends Animation { }
The user is on the start screen with the root fragment Lets say the root fragment is contained in Activity A. He selects an item on the root fragment which then displays a new fragment to show details of that item. It does so using a fragment transaction that sets animations both for the push and the pop case (so when the user presses the back button, the transition is animated) The transaction is added to the back stack. Which means that when the back button is pressed from detail fragment, the popping process is animated. From this fragment he starts an activity which (for whatever reason) deletes the item that was just shown. Lets say it is Activity B When this activity finishes, I would like to return to the root fragment without showing the "pop animation" of the "detail fragment" One way of getting this behavior is by doing this in Activity B : Intent intent = new Intent(this, A.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish(); This will start the Activity A resetting it to its root state according to the documentation.(check the last paragraph in the section which says "This launch mode can also be used to good effect in conjunction with FLAG_ACTIVITY_NEW_TASK:......") With this configuration, the animation will be present in the default case while in the special case you can control the animation using : intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); Which starts new activity without any animations. If you do want any animation, you can do it using the overridePendingTransition method.
Android actually now has a way to do this without the work around #Geoff answered. To avoid the animation to run on popBackStack(), when inflating your fragments add .setReorderingAllowed(true) to your fragmentTransaction. So for example: supportFragmentTransaction.beginTransaction() .setReorderingAllowed(true) .addToBackStack(null) .setCustomAnimations( android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out ) .replace(yourContainer.id, yourFragment) .commit() You'll notice that if you set setReorderingAllowed(true), the pop animation would no longer play. The results are actually similar to the result of #Geoff's answer.
So, I'd like to suggest a small change to #Geoff's answer. Instead of having a global static boolean, I'd rather have a local non-static one. This is what I came up with. Create an interface public interface TransitionAnimator { void disableTransitionAnimation(); void enableTransitionAnimation(); } Make the fragment implement that interface. public class MyFragment extends Fragment implements TransitionAnimator { private boolean mTransitionAnimation; #Override public void disableTransitionAnimation() { mTransitionAnimation = false; } #Override public void enableTransitionAnimation() { mTransitionAnimation = true; } #Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { Animation result; if (!mTransitionAnimation) { Animation dummyAnimation = new Animation() { }; dummyAnimation.setDuration(0); result = dummyAnimation; } else { result = super.onCreateAnimation(transit, enter, nextAnim); } return result; } And then, when you want to disable the transition animations for a fragment, just do if (fragment instanceof TransitionAnimator) { ((TransitionAnimator) fragment).disableTransitionAnimation(); } to enable them, just do if (fragment instanceof TransitionAnimator) { ((TransitionAnimator) fragment).enableTransitionAnimation(); } If you want to do the same for all the fragments in the fragment manager, just do List<Fragment> fragments = getSupportFragmentManager().getFragments(); for (Fragment fragment : fragments) { if (fragment instanceof TransitionAnimator) { // disable animations ((TransitionAnimator) fragment).disableTransitionAnimation(); } } Very similar, but without static fields.
Just use another overloaded method of setCustomAnimation() and in which do not set the R.anim.slide_out and that will solve your problem Cheers :)
Before answering your question, I need to ask a question myself. In the onBackPressed() method of the second activity, can you access the backstack of the first activity? If yes, then you can call popBackStackImmediate(String trnaisiotnName, int inclusive) and it will remove the fragment transition from the backstack, and you dont need to worry about animations. I am assuming you can access backstack of the previous activity, otherwise this wont work
This is fairly easy to achieve through overridePendingTransition(int enterAnim, int exitAnim) with both 0 for no animation. FragmentManager fm = getSupportFragmentManager(); if (fm.getBackStackEntryCount() > 0) { fm.popBackStack(); overridePendingTransition(0, 0); }
This is a follow-up to #Geoff's excellent answer, but fitted for a more dynamic and real-live scenario. I imagined this being a nice little post, but I realize now that it got a little out of hand. However, the code is all there and I find it really useful, though it covers a lot more than just how to disable transition animations. Usually, when I work with Fragments I like to have a BaseFragment that attaches to a BaseActivityCallback. This BaseActivityCallback can be used by the my Fragments to add a new Fragment on top of itself, or even to pop Fragments beneath it, hence the desire to disable pop animations -- or pop silently: interface BaseActivityCallback { void addFragment ( BaseFragment f, int containerResId ); void popFragment ( boolean silently ); } class BaseActivity extends android.support.v4.app.FragmentActivity implements BaseActivityCallback { public void addFragment ( BaseFragment f, int containerResId ) { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.enter, R.anim.pop_exit); // http://stackoverflow.com/a/17488542/2412477 ft.addToBackStack(DEFAULT_FRAGMENT_STACK_NAME); ft.replace(containerResId, fragment); ft.commitAllowingStateLoss(); } public void popFragment ( boolean silently ) { FragmentManager fm = getSupportFragmentManager(); if ( silently ) { int count = fm.getFragments().size(); BaseFragment f = (BaseFragment)fm.getFragments().get(count-1); f.setDisableTransitionAnimations(true); } fm.popBackStackImmediate(); } } public abstract class BaseFragment extends android.support.v4.app.Fragment { private static final String TAG = "BaseFragment"; private final String STATE_DISABLE_TRANSITION_ANIMATIONS = TAG+".stateDisableTransitionAnimations"; protected BaseActivityCallback baseActivityCallback; private boolean disableTransitionAnimations; #Override public void onCreate ( #Nullable Bundle savedInstanceState ) { super.onCreate(savedInstanceState); disableTransitionAnimations = (savedInstanceState==null ? false : savedInstanceState.getBoolean(STATE_DISABLE_TRANSITION_ANIMATIONS, false)); } #Override public void onAttach ( Context context ) { super.onAttach(context); baseActivityCallback = (BaseActivityCallback)context; } #Override public void onSaveInstanceState ( Bundle outState ) { super.onSaveInstanceState(outState); outState.putBoolean(STATE_DISABLE_TRANSITION_ANIMATIONS, disableTransitionAnimations); } #Override public Animation onCreateAnimation ( int transit, boolean enter, int nextAnim ) { if ( disableTransitionAnimations ) { Animation nop = new Animation(){}; nop.setDuration(0); return nop; } return super.onCreateAnimation(transit, enter, nextAnim); } public void setDisableTransitionAnimations ( boolean disableTransitionAnimations ) { this.disableTransitionAnimations = disableTransitionAnimations; // http://stackoverflow.com/a/11253987/2412477 } } Now you can create your MainActivity and have that show a Fragment1 which can add another Fragment2 which may in turn pop Fragment1 silently: public class MainActivity extends BaseActivity { protected void onCreate ( Bundle savedInstanceState ) { setContentView(R.layout.main_activity); ... if ( getSupportFragmentManager().getFragments() != null && !getSupportFragmentManager().getFragments().isEmpty() ) { addFragment( FragmentA.newInstance(), R.id.main_activity_fragment_container ); } } ... } public class FragmentA extends BaseFragment { public View onCreateView ( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) { ViewGroup root = (ViewGroup)inflater.inflate(R.layout.fragment_a, container, false); ... root.findViewById(R.id.fragment_a_next_button) .setOnClickListener( new View.OnClickListener() { public void onClick ( View v ) { baseActivityCallback.addFragment( FragmentB.newInstance(), R.id.main_activity_fragment_container ); } }); } } public class FragmentB extends BaseFragment { public View onCreateView ( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) { ViewGroup root = (ViewGroup)inflater.inflate(R.layout.fragment_b, container, false); ... root.findViewById(R.id.fragment_b_pop_silently_button) .setOnClickListener( new View.OnClickListener() { public void onClick ( View v ) { baseActivityCallback.popFragment( true ); } }); } }
Override this in the fragment that you want to pop without animation and still keep the animation when you enter #Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { if(!enter){ Animation a = new Animation() {}; a.setDuration(0); return a; } return super.onCreateAnimation(transit, enter, nextAnim); }
Easier solution: for (fragment in supportFragmentManager.fragments) { removeFragment(fragment) } if (supportFragmentManager.backStackEntryCount > 0) { supportFragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) }
Reply to Geoff and plackemacher comment. You can try to remove all views from this Fragment. Then fragment will show but it should be transparent. Remove all-1 (I use navigate drawer so drawer fragment should stay) fragment: int size = fragmentsList.size ()-1; FragmentTransaction transaction = fragmentManager.beginTransaction (); transaction.setTransition (FragmentTransaction.TRANSIT_NONE); Fragment fragment; for (int i = size ; i > 0 ; i--) { fragment = fragmentsList.get (i); if(fragment != null) { View viewContainer = fragment.getView (); if (viewContainer != null) { ((ViewGroup) viewContainer).removeAllViews (); } transaction.remove (fragment); } } size = fragmentManager.getBackStackEntryCount (); for (int i = 0; i < size ; i++) { fragmentManager.popBackStack (null, FragmentManager.POP_BACK_STACK_INCLUSIVE); } Sorry for my English