9

Background

I have an activity with a fragment that needs to be animated when being created, but not when the orientation changes .

The fragment is being inserted into a layout dynamically, as it's a part of a navigation-drawer-style activity.

The problem

I wanted to avoid re-creating the fragment for configuration changes, so I used setRetainInstance in the fragment. It works, but for some reason the animation also restarts each time I rotate the device.

What I've done

I've added this to the fragment:

@Override
public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
}

and this to the activity:

    final FragmentManager fragmentManager = getSupportFragmentManager();
    final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    MyFragment fragment= (MyFragment) fragmentManager.findFragmentByTag(MyFragment.TAG);
    if (fragment== null) {
        fragmentTransaction.setCustomAnimations(R.anim.slide_in_from_left, R.anim.slide_out_to_right);
        fragment= new MyFragment();
        fragmentTransaction
                .add(R.id.fragmentContainer, fragment, MyFragment.TAG).commit();
    }

fragment_container.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragmentContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

What I've tried

  • I've tried to fix it by using "replace" instead of "add". It didn't help.
  • I've also tried to always perform the replacement of the fragment, and if the fragment is already there, do it without animation (on the same fragment).
  • if I remove the setRetainInstance call, it works, but I want to avoid re-creating the fragment.

Question

  1. How can I solve this issue?
  2. Why do I still get an animation for the adding of the fragment?
  3. What happens when other configurations change?

Workaround #1

This solution works in general, but it causes bad things to the lifecycle you've tried to achieve :

    MyFragment fragment= (MyFragment) fragmentManager.findFragmentByTag(MyFragment.TAG);
    if (MyFragment== null) {
        MyFragment= new MyFragment();
        fragmentManager.beginTransaction().setCustomAnimations(R.anim.slide_in_from_left, R.anim.slide_out_to_right)
                .replace(R.id.fragmentContainer, fragment, MyFragment.TAG).commit();
    } else {
        //workaround: fragment already exists, so avoid re-animating it by quickly removing and re-adding it:
        fragmentManager.beginTransaction().remove(fragment).commit();
        final Fragment finalFragment = fragment;
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                fragmentManager.beginTransaction().replace(R.id.fragmentContainer, fragment, finalFragment .TAG).commit();
            }
        });
    }

I would still want to see what can be done, because this can cause things you didn't want to occur(onDetach for the fragment, for example).

Workaround #2

One way to solve this is to avoid adding the animation via the fragmentManager, and just do it for the view itself within the fragment lifecycle. This is how it looks like:

BaseFragment

@Override
public void onViewCreated(final View rootView, final Bundle savedInstanceState) {
    super.onViewCreated(rootView, savedInstanceState);
    if (savedInstanceState == null)
        rootView.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.slide_in_from_left));
}


@Override
public void onDestroyView() {
    super.onDestroyView();
    if (!getActivity().isChangingConfigurations())
        getView().startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.fade_out));
}
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • `if I remove the setRetainInstance call, it works`, how does it work then? You mean you need to re-create and re-add the fragment on every orientation change (with or without an animation)? – Simas Mar 11 '15 at 21:04
  • @user3249477 Yes. The fragment will be re-created. – android developer Mar 11 '15 at 21:15
  • Is your activity being destroyed and recreated every time the orientation changes? – Itai Hanski Mar 13 '15 at 09:55
  • According to @ItaiHanski, plz post your manifest file – Hugo Gresse Mar 13 '15 at 09:55
  • You guys, there is nothing special in the manifest. it's just a simple activity with a fragment in it, created in the same way I've written. You change the orientation, and the activity is being re-created, while the fragment should still be used for it. – android developer Mar 13 '15 at 10:49
  • Have you considered preventing the activity from being re-created on rotation? This would also keep the fragment and thus, it wouldn't animate again. – Sebastiano Mar 14 '15 at 19:26
  • @dextor it is possible, but that's not considered a good practice. – android developer Mar 14 '15 at 19:30
  • @androiddeveloper True, but still, there are some situations where you have no other choices. I've used that approach in a few occasions with my company's application: other than the the pain of handling all the layout changes by code, I haven't found any major drawbacks. – Sebastiano Mar 14 '15 at 19:32
  • @androiddeveloper How about you override the `onCreateAnimation()` method and prevent the animation from happening if the fragment is being recreated after a rotation? As shown here: http://stackoverflow.com/questions/23016517/how-to-disable-avoid-fragment-custom-animations-after-screen-rotation – Sebastiano Mar 14 '15 at 19:34
  • @dextor That's also true. When using OpenGL on games and when using WebView, it's still impossible to handle those situations without the special flags on the manifest. But in normal cases, it's recommended to treat it correctly. – android developer Mar 14 '15 at 19:35
  • @dextor About the link, this looks promising. Have you tried it on the case I've wrote about? It still looks weird, like the other solutions I've found, but maybe I will use it instead of what I did (which is workaround #2 for now). – android developer Mar 14 '15 at 19:36
  • @androiddeveloper I haven't tried it, but it looks like the best (if not the cleanest) way of achieving what you want. Let me know if that works. – Sebastiano Mar 14 '15 at 19:38
  • I think this answer can help you: [fragments and onConfigurationChanged][1] [1]: http://stackoverflow.com/a/16411725/1859161 – adsion Mar 18 '15 at 11:39

4 Answers4

9

How about you override the onCreateAnimation() method and prevent the animation from happening if the fragment is being recreated after a rotation?

As shown here: How to disable/avoid Fragment custom animations after screen rotation


EDIT: Here's a sample code:

BaseFragment.java

...
private boolean mNeedToAvoidAnimation;

@Override
public void onDestroyView() {
    super.onDestroyView();
    mNeedToAvoidAnimation = true;
}

@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
    // This avoids the transaction animation when the orienatation changes
    boolean needToAvoidAnimation = mNeedToAvoidAnimation;
    mNeedToAvoidAnimation = false;
    return needToAvoidAnimation ? new Animation() {
    } : super.onCreateAnimation(transit, enter, nextAnim);
}

This fragment should be the base one that all fragments in this activity will extend.

Community
  • 1
  • 1
Sebastiano
  • 12,289
  • 6
  • 47
  • 80
0

take a look at your code again:

final FragmentManager fragmentManager = getSupportFragmentManager();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
MyFragment fragment= (MyFragment)
    fragmentManager
   .findFragmentByTag(MyFragment.TAG);
if (fragment== null) {
    fragmentTransaction.setCustomAnimations(R.anim.slide_in_from_left, R.anim.slide_out_to_right);
    fragment= new MyFragment();
    fragmentTransaction
            .add(R.id.fragmentContainer, fragment,
                 PhoneInsertionFragment.TAG).commit();
}

you're using MyFragment.TAG to find the fragment, but you're adding to transaction with PhoneInsertionFragment.TAG.

That's why you're getting the animation and the fragment re-creating every time. Just use the same tag for find and add and it should be fine.

Budius
  • 39,391
  • 16
  • 102
  • 144
  • This is actually the same fragment. I renamed it here but it seems I've forgot to rename it everywhere. Will now update the code. It's not the cause for the issue. In short, what you've found is a typo I had while writing the question, not in the real code. – android developer Mar 03 '15 at 13:50
0

Why it is occurring because when fragment manager tries to re-attach the fragment on orientation, it applies the animations which already stored in the transaction. (i.e which you set when the fragment was added).

We solved this by handling orientation change in activity itself. If you want to change the UI design, attach and detach the fragment in onConfigChanges method.

Ponsuyambu
  • 7,876
  • 5
  • 27
  • 41
  • so you say it's this way on purpose? does it mean you are one of Google's developers? If so, can you please add the ability to override this behavior? – android developer Mar 17 '15 at 12:32
0

I studied the code, looks like the animation information is saved in Fragment.animationInfo, this value is set by

BackStackState.executeOps() -> Fragment.setNextAnim(int animResourceId)

By default, re-creating the fragment will discard the animation information, but if you set fragment retainInstance, the animation information will also is retained

My solution:

class TestFragment : Fragment() {
    override fun onDetach() {
        super.onDetach()
        if (retainInstance) {
            // use reflect clear retain Animation
            reflectField<Fragment>("mAnimationInfo").set(this, null)
        }
    }

    @Throws(NoSuchFieldException::class, SecurityException::class)
    inline fun <reified T : Any> reflectField(name: String): Field {
        val field = T::class.java.getDeclaredField(name)
        field.isAccessible = true
        return field
    }
}

I only tested support.v4.app.Fragment, but I think this solution also applies to android.app.Fragment

OAO
  • 11
  • 3