I want to animate the removal of fragment.
I tried:
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.push_down_in, R.anim.push_up_out)
.remove(myFragment)
.commit();
but the fragment just disappears.
I noticed the out animation only plays with 'replace', so I tried to replace the fragment with an empty Fragment like this:
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.push_down_in, R.anim.push_up_out)
.replace(viewId, new Fragment())
.commit();
But it still just disappears disappears.
So, how can I animate the removal of fragment?
I saw this when I was having similar problems and just thought Id drop a quick note.
Rather than creating a dummy fragment in order to replace the existing one I think you should animate the current fragments view. When the animation finishes you can simply remove the fragment.
This is how i did it:
final FragmentActivity a = getSherlockActivity();
if (a != null) {
//Your animation
Animation animation = AnimationUtils.loadAnimation(a, R.anim.bottom_out);
animation.setDuration(getResources().getInteger(android.R.integer.config_shortAnimTime));
//You can use AnimationListener, MagicAnimationListener is simply a class extending it.
animation.setAnimationListener(new MagicAnimationListener() {
#Override
public void onAnimationEnd(Animation animation) {
//This is the key, when the animation is finished, remove the fragment.
try {
FragmentTransaction ft = a.getSupportFragmentManager().beginTransaction();
ft.remove(RestTimerFragment.this);
ft.commitAllowingStateLoss();
} catch (Exception e) {
e.printStackTrace();
}
}
});
//Start the animation.
getView().startAnimation(animation);
}
I figured it out.
The exiting view is animated on the canvas of the entering view so if there is no entering canvas there is no canvas for the animation.
To show the animation I had to always use replace and use entering fragments of the same size to those exiting. After the animation is finished I set the view of the new fragments to gone.
You could animate the removal by setting this custom animation to the fragmentTransaction
fragmentTransaction.setCustomAnimations(R.anim.right_in, R.anim.defff,R.anim.defff,R.anim.right_out);
The third and fourth params are for removing the fragment
I got inspiration from Zoltish answer , this is my implementation:
1.add this method inside the fragment , it will animate the fragment out of the screen to the left:
public void animateOut()
{
TranslateAnimation trans=new TranslateAnimation(0,-300*Utils.getDensity(getActivity()), 0,0);
trans.setDuration(150);
trans.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationRepeat(Animation animation) {
// TODO Auto-generated method stub
}
#Override
public void onAnimationEnd(Animation animation) {
// TODO Auto-generated method stub
((BetsActivty)getActivity()).removeFrontFragmentAndSetControllToBetting();
}
});
getView().startAnimation(trans);
}
The method that inside onAnimationEnd() removes the fragment like this:
getSupportFragmentManager().beginTransaction().
remove(getSupportFragmentManager().findFragmentById(R.id.fragment_container)).commit();
2.call the animateOut of the fragment from onBack() of the activity.
Cheers
by the way my getDensity() is:
public static int getDensity(Context context)
{
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int)metrics.density;
}
with it i calculate the DP value for the current running Device.
Replacing with an empty fragment, before the point of insertion of the next fragment and also delaying the insertion of the next fragment (by 200ms) so that the exit animation of the blank fragment can play, solved my problem.
This the code to insert an empty fragment with exit animation.
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.exit, R.anim.pop_exit)
.replace(R.id.fragmentLayout, new Fragment())
.commit();
Exit.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="-100%"
android:interpolator="#android:anim/accelerate_interpolator"
android:duration="200"/>
</set>
pop_exit.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="100%"
android:interpolator="#android:anim/accelerate_interpolator"
android:duration="200"/>
</set>
I agreed with hugoc and here some code
to solve it
public class MyActivity extends Activity {
//Some thing
public void Fragment2BackToFragment1(Fragment fragment1, Fragment fragment2) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
animateExit(fragment2);
ft.replace(R.id.main_content, fragment1, "fragment1");
ft.commit();
}
private void animateExit(Fragment exitFragment) {
if (exitFragment != null) {
final View view = exitFragment.getView();
if (view != null) {
int anim = R.anim.fade_out;
Animation animation =
AnimationUtils.loadAnimation(getActivity(), anim);
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
view.postDelayed(new Runnable() {
#Override
public void run() {
view.setVisibility(View.GONE);
}
}, 300);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
view.startAnimation(animation);
}
}
}
}
An easy fix below:
1-Call animate on fragment.getView().
2-Remove fragment inside onAnimationEnd().
final Fragment frag= getSupportFragmentManager().findFragmentById(R.id.fragmentContainer);
frag.getView().animate().alpha(0f).scaleX(0f).scaleY(0f)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
getSupportFragmentManager()
.beginTransaction()
.remove(frag)
.commit();
}
}).start();
reason as #hugoc said
The exiting view is animated on the canvas of the entering view so if there is no entering canvas there is no canvas for the animation.
To show the animation I had to always use replace and use entering fragments of the same size to those exiting. After the animation is finished I set the view of the new fragments to gone.
below is actual code:
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_bottom, R.anim.slide_out_top);
transaction.hide(removeFragment).add(R.id.fragment_container,addFragment).commit();
transaction = manager.beginTransaction();
transaction.remove(removeFragment).commit();
What enter:
it is new fragment which must be show
What exit:
it is current fragment which must be hide
What popEnter:
it is previous fragment which must be show
What popExit:
it is current fragment which must be hide
For use these animations, you should target them on show or hide transaction commands. Exit animations doesn't work on remove/replace procedures.
setCustomAnimations(enter, exit, popEnter, popExit) support enter and exit animation, So set four animations and must keep it before transaction.replace()
Kotlin:
val manager = supportFragmentManager
val transaction = manager.beginTransaction()
transaction.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right ,
android.R.anim.slide_in_left, android.R.anim.slide_out_right)
transaction.commit()
So the easy way:
When you open a fragment (called from parent Activity):
FragmentA fragment = new FragmentA();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_up, R.anim.slide_down);
transaction.add(android.R.id.content, fragment);
transaction.addToBackStack(null);
transaction.commit();
Specify enter and exit transactions
transaction.setCustomAnimations(R.anim.slide_up, R.anim.slide_down);
When closing a fragment (called from inside the fragment)
getActivity().getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.slide_up, R.anim.slide_down).remove(this).commit();
Specify enter and exit transactions
setCustomAnimations(R.anim.slide_up, R.anim.slide_down)
See different variants of animations in https://developer.android.com/training/basics/fragments/animate.
On any event (button click, time out, etc.) you can write inside a fragment:
parentFragmentManager.beginTransaction()
.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right)
.remove(this)
.commitAllowingStateLoss()
// parentFragmentManager.popBackStack() - might be needed if the fragment keeps visible after removing.
Just because I spent time on it and I like my extension function: (Kotlin)
fun FragmentManager.removeByTagAnimated(tag: String, animation: Int){
findFragmentByTag(tag)?.let { fragToRemove ->
val anim = AnimationUtils.loadAnimation(fragToRemove.context, animation).apply {
setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {}
override fun onAnimationRepeat(animation: Animation?) {}
override fun onAnimationEnd(animation: Animation?) {
commit {
remove(fragToRemove)
}
}
})
}
fragToRemove.view?.startAnimation(anim)
}
}
Animation is an R.anim resource like in this page
Just use supportFragmentManager.removeByTagAnimated(MY_FRAGMENT_TAG, R.anim.my_exit_animation) in your Activity.
Related
I want to reach a specific fragment transition behaviour ,as I mention in the question, I want my fragment to appears from the right and disappears in the same direction back to the point it starts from..
These are my xml animations :
right_to_left.xml :
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="1000"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
left_to_right.xml
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="-100%"
android:toYDelta="0%" />
and here are the different ways that I've tried to achieve the fragment animation :
When the fragment appears :
productHolder.fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putSerializable(Utils.productDetailsKey, productPojo);
ProductDetailsFragment productDetailsFragment = new ProductDetailsFragment();
productDetailsFragment.setArguments(bundle);
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.product_animation_enter, R.anim.product_animation_enter);
transaction.add(R.id.newContainer, productDetailsFragment);
transaction.commit();
MainActivity.transparent.setBackgroundColor(ContextCompat.getColor(context, R.color.blackTransparent));
}
});
When the fragment disappears method 1 :
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.product_animation_exit, R.anim.product_animation_exit);
transaction.remove(ProductDetailsFragment.this);
transaction.commitAllowingStateLoss();
MainActivity.transparent.setBackgroundColor(0x00000000);
}
});
When the fragment disappears method 2 :
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.product_animation_exit);
animation.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
try {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.remove(ProductDetailsFragment.this);
transaction.commitAllowingStateLoss();
MainActivity.transparent.setBackgroundColor(0x00000000);
} catch (Exception e) {
}
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
getView().startAnimation(animation);
}
});
unfortunately none of those worked as I expect,
May this image can explain more what I want : image
Thank you in advance.
I would suggest, first you make a working solution the first part of your problem, that is switching between fragments, and then you add the second part, which is the animation.
1. Switching fragments
In your XML layout file you take out everything belonging to the fragments and add a Framelayout.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In your code you get a reference to the FrameLayout like so:
int containerId = R.id.fragment_container;
Then you have two methods, one for adding the first fragment and one for switching back and forth between the two.
The add method goes like this:
void addFrag(int containerId, Fragment firstFragment){
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(containerId, firstFragment);
transaction.commit();
}
The replace method goes like this:
void replaceFrag(int containerId, Fragment newFragment){
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(containerId, newFragment);
transaction.commit();
}
As long as you pass the right fragment, you can use this method for replacing any of your fragments with the other one. Edit You can also use the replaceFrag method only, even for adding a fragment to the container for the first time. End of edit
2. Animation
That's really the easy part. You don't need to make your own XML, since sliding animations are built into Android (so use the exact IDs I am providing here). All you have to do is to add one line of code before you add or replace your fragments:
transaction.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right);
Edit Since these built-in animations start from 50%, however, they don't look too nice, in my opinion. You can simply use your XMLs:
transaction.setCustomAnimations(R.anim.left_to_right, R.anim.right_to_left);
End of Edit
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.
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.
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
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