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:36

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>
查看更多
倾城 Initia
3楼-- · 2020-01-26 13:39

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).

查看更多
smile是对你的礼貌
4楼-- · 2020-01-26 13:39

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.

查看更多
贼婆χ
5楼-- · 2020-01-26 13:39

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