SetRetainInstance (true) + setCustomAnimations (...) = animation for each orientation change?

Background

I have activity with a fragment that needs to be animated when creating, but not when changing orientation.

The fragment is inserted dynamically into the layout, as it is part of the activity of the navigation box style.

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 every time the device is rotated.

What I've done

I added this to the snippet:

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

and this is for 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 tried

  • I tried to fix this by using instead of "add" instead of "replace" instead of "replace". It did not help.
  • I also tried to always replace a fragment, and if the fragment already exists, do it without animation (on the same fragment).
  • If I delete the setRetainInstance call, it works, but I want to avoid re-creating the fragment.

Question

  • How can I solve this problem?
  • Why am I still getting animation to add a fragment?
  • What happens when other configurations change?

Workaround โ„–1

This solution works in general, but it harms the life cycle that you 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 like to see what can be done, because it can lead to the fact that you do not want to meet (for example, onDetach for a fragment).

Workaround number 2

One way to solve this problem is to not add animation through the fragmenter and just do it for the view itself inside the fragment life cycle. Here's what 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)); } 
+7
android animation android-fragments
source share
4 answers

How about overriding the onCreateAnimation() method and preventing the animation if the fragment is recreated after the rotation?

As shown below: How to disable / exclude custom fragment animations after rotating the screen


EDIT: Here is 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 must be basic so that all fragments in this activity expand.

+9
source share

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 use MyFragment.TAG to search for a fragment, but you add to the transaction with PhoneInsertionFragment.TAG .

That's why you get an animation and a snippet every time. Just use the same tags for find and add , and everything should be fine.

0
source share

Why this happens because when the fragment manager tries to re-attach the fragment to the orientation, it applies animations that are already stored in the transaction. (that is, you set when the fragment was added).

We solved this by processing the orientation changes in the activity itself. If you want to change the user interface design, attach and detach the fragment in the onConfigChanges method.

0
source share

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

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

By default, re-creating the fragment will discard the animation information, but if you set the keepInstance fragment, the animation information will also be saved

My decision:

 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 the support.v4.app.Fragment file, but I think this solution also applies to android.app.Fragment

0
source share

All Articles