Android 嵌套片段在过渡动画期间消失

Android 嵌套片段在过渡动画期间消失,android,android-fragments,android-support-library,android-nested-fragment,Android,Android Fragments,Android Support Library,Android Nested Fragment,下面是场景:活动包含片段A,它反过来使用getChildFragmentManager()在其onCreate中添加片段A1和A2,如下所示: getChildFragmentManager() .beginTransaction() .replace(R.id.fragmentOneHolder, new FragmentA1()) .replace(R.id.fragmentTwoHolder, new FragmentA2()) .commit() 到目前为止,一切正常

下面是场景:活动包含片段
A
,它反过来使用
getChildFragmentManager()
在其
onCreate
中添加片段
A1
A2
,如下所示:

getChildFragmentManager()
  .beginTransaction()
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()
到目前为止,一切正常

然后,我们在活动中运行以下事务:

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .replace(R.id.fragmentHolder, new FragmentB())
  .addToBackStack(null)
  .commit()
在转换过程中,片段
B
enter
动画运行正常,但片段A1和A2完全消失。当我们使用后退按钮还原事务时,它们将正确初始化,并在
pocenter
动画期间正常显示

在我的简短测试中,它变得更奇怪了——如果我为子片段设置动画(见下文),那么当我们添加片段
B

getChildFragmentManager()
  .beginTransaction()
  .setCustomAnimations(enter, exit)
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()
我想要实现的效果很简单-我想要在片段
A
(anim2)上运行
exit
(或者应该是
popExit
?)动画,为整个容器(包括其嵌套的子容器)设置动画

有没有办法做到这一点

编辑:请查找测试用例


Edit2:感谢@StevenByle促使我继续尝试静态动画。显然,您可以基于每个操作设置动画(而不是整个事务的全局),这意味着子级可以有一个不确定的静态动画集,而其父级可以有不同的动画,整个事务可以在一个事务中提交。请参阅下面的讨论和。

我理解这可能无法完全解决您的问题,但可能会满足其他人的需要,您可以将
enter
/
exit
pocenter
/
popExit
动画添加到您的孩子
Fragment
s中,这些动画实际上不会移动
片段。只要动画的持续时间/偏移量与其父级
片段
动画相同,它们就会显示为随父级动画一起移动/设置动画。

为了避免用户在可以“模拟”的事务中移除/替换父级片段时看到嵌套片段消失当这些碎片出现在屏幕上时,通过提供它们的图像,它们仍然存在。此图像将用作嵌套片段容器的背景,因此即使嵌套片段的视图消失,图像也将模拟它们的存在。此外,我不认为失去与嵌套片段视图的交互性是一个问题,因为我认为当它们刚刚被删除时,您不会希望用户对它们进行操作(可能也是一种用户操作)


我对设置背景图像(一些基本的东西)做了一些尝试。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@

编辑: 我最终没有实现这个解决方案,因为还有其他问题。Square最近推出了两个库来替换片段。我想说,这实际上可能是一个更好的选择,而不是试图将碎片破解成谷歌不希望他们做的事情


@@@@@@@@@@@@@@@@@@@@@@@@@@@@

我想我提出这个解决方案是为了帮助将来有这个问题的人。如果您跟踪原始海报与其他人的对话,并查看他发布的代码,您将看到原始海报最终得出结论,即在为父片段设置动画的同时对子片段使用不可操作动画。这个解决方案并不理想,因为它迫使您跟踪所有子片段,这在使用带有FragmentPagerAdapter的ViewPager时可能会很麻烦

由于我在所有地方都使用子片段,因此我提出了一种高效的模块化解决方案(因此可以轻松删除),以防他们修复它,并且不再需要这种无操作动画。

有很多方法可以实现这一点。我选择使用单例,我称之为ChildFragmentAnimationManager。它基本上会根据父片段为我跟踪子片段,并在被要求时对子片段应用无操作动画

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;
}

}
现在所有片段都由父片段存储在内存中,您可以像这样对它们调用animate,并且您的子片段不会消失

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();
另外,正如您所知道的,这里是存放在res/anim文件夹中的no_anim.xml文件:

<?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>


同样,我不认为这个解决方案是完美的,但它比每个实例都有一个子片段要好得多,在父片段中实现自定义代码以跟踪每个子片段。我去过那里,一点也不好玩。

为了清晰起见,我发布了我的解决方案。 解决办法很简单。如果您试图模拟父级的片段事务动画,只需向子片段事务添加具有相同持续时间的自定义动画即可。哦,请确保在添加()之前设置自定义动画

R.anim.none的xml(我父母进入/退出动画的时间为250ms)


我认为我找到了一个更好的解决方案,而不是像Luksprog建议的那样将当前片段快照到位图

诀窍是隐藏要移除或分离的片段,只有在动画完成后,片段才会在其自身的片段事务中移除或分离

假设我们有
FragmentA
FragmentB
,两者都有子片段。现在,您通常会执行以下操作:

getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(动画1、动画2、动画1、动画2)
.add(R.id.fragmentHolder,新的FragmentB())

我想出了一个非常干净的解决办法。在我看来,这是最简单的解决方案,虽然从技术上讲这是“绘制位图”的解决方案,但至少它是由片段库抽象出来的

<?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>
getChildFragmentManager().beginTransaction()
        .setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none)
        .add(R.id.container, nestedFragment)
        .commit();
<?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>
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);
}
// 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;
    }
}
<?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>
@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;
}
<resources>
  <integer name="keep_child_fragment_animation_duration">500</integer>
</resources>
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();
   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()
}
@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);
}
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);
    }
}
            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()
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()
}