Nested fragments disappear during transition anima

2020-01-26 12:48发布

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.

16条回答
霸刀☆藐视天下
2楼-- · 2020-01-26 13:16

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>
查看更多
时光不老,我们不散
3楼-- · 2020-01-26 13:19

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.

查看更多
倾城 Initia
4楼-- · 2020-01-26 13:23

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

查看更多
疯言疯语
5楼-- · 2020-01-26 13:23

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.

查看更多
萌系小妹纸
6楼-- · 2020-01-26 13:25

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.

查看更多
够拽才男人
7楼-- · 2020-01-26 13:29

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();
查看更多
登录 后发表回答