过渡动画期间嵌套片段消失(Nested fragments disappear during tra

2019-07-20 00:18发布

这里的情形:活性片段包含A ,这反过来使用getChildFragmentManager()添加片段A1A2在其onCreate像这样:

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

在转换期间,将enter的动画对片段B正确运行但片段A1和A2完全消失 。 当我们向后退按钮恢复交易,他们在过程中正确和正常显示初始化popEnter动画。

在我简短的测试中,它得到了怪异-如果我设置为子片段(见下文)的动画时, exit动画间歇运行时,我们添加片段B

getChildFragmentManager()
  .beginTransaction()
  .setCustomAnimations(enter, exit)
  .replace(R.id.fragmentOneHolder, new FragmentA1())
  .replace(R.id.fragmentTwoHolder, new FragmentA2())
  .commit()

我想达到的效果是很简单-我想exit (或它应该是popExit ?)的动画片段A (anim2)运行,动画整个容器,包括其嵌套的孩子。

有没有什么办法来实现这一目标?

编辑 :请找一个测试用例这里

EDIT2:感谢@StevenByle推我保持与静态动画努力。 显然,你可以在每个运算基础(不是全球整个交易),这意味着孩子们可以无限期静态动画集设置动画,而他们的父母可以有不同的动画和整个事情可以在一个事务被提交。 请参见下面的讨论和更新测试用例项目 。

Answer 1:

为了避免用户看到嵌套片段消失当父片段被去除/在一个事务中替换你可以“模拟”那些片段仍存在通过提供他们的图像,因为它们出现在屏幕上。 这个图像将被用作嵌套片段容器所以即使嵌套片段的视图消失的图像将模拟它们的存在的背景。 另外,我不认为失去与分片嵌套的意见,问题的互动,因为我不认为你会希望用户作用于他们时,他们只是要被删除(可能是因为用户操作的过程好)。

我做了一个小例子与建立的背景图片(基本的东西)。



Answer 2:

因此,似乎有很多关于此不同的解决方法,但基于@ Jayd16的答案,我想我已经找到了一个非常坚实的包罗万象的解决方案,还允许对孩子的片段自定义过渡动画,并且不需要做布局的位图缓存。

BaseFragment扩展类的Fragment ,并让所有的碎片继承了该类(不只是孩子的片段)。

在这种BaseFragment类,添加以下内容:

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

它,不幸的是,需要反思; 然而,由于这种解决方法是为支持库,你不会,除非你更新你的支持库运行底层实现变化的风险。 如果您正在构建从源支持库,你可以添加一个访问下一个动画资源ID Fragment.java和删除反思的必要。

该解决方案消除了需要“猜测”父母的动画持续时间(这样“什么都不做”的动画将具有相同的持续时间与父母的退出动画),并允许您还是做孩子的片段自定义动画(例如,如果你”再有不同的动画左右交换子片段)。



Answer 3:

我能拿出一个漂亮干净的解决方案。 IMO其至少哈克,虽然这在技术上的“绘制位图”的解决方案由片段LIB至少其抽象。

确保你的孩子断肢覆盖一个父类与此:

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

如果我们对孩子的frag退出动画,他们将动画,而不是闪烁的路程。 我们可以通过具有简单地吸引孩子的片段在全alpha一段时间的动画利用这一点。 通过这种方式,他们会在父片段,因为它动画,获得所需的行为保持可见。

我能想到的唯一的问题是跟踪的是持续时间。 我也许可以将其设置为一个大十岁上下的号码,但我担心可能有性能问题,如果它仍然绘制动画的地方。



Answer 4:

进出口张贴我为了清晰的解决方案。 解决的办法很简单。 如果你正在试图模仿父母的片段事务动画只是单纯的自定义动画添加到与相同期限的孩子片段交易。 哦,请确保您设置的自定义动画之前添加()。

getChildFragmentManager().beginTransaction()
        .setCustomAnimations(R.anim.none, R.anim.none, R.anim.none, R.anim.none)
        .add(R.id.container, nestedFragment)
        .commit();

对于R.anim.none XML(可我的父母进入/退出动画时间是250毫秒)

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


Answer 5:

我理解这可能无法完全解决你的问题,但也许它会满足别人的需求,你可以添加enter / exitpopEnter / popExit动画给你的孩子Fragment s表示不实际移动/动画Fragment秒。 只要动画具有相同的时间/其父偏移Fragment动画,它们将出现移动/与父母的动画动画。



Answer 6:

您可以在子片段做到这一点。

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


Answer 7:

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

编辑:我结束了unimplementing这个解决方案有,这有其他的问题。 广场最近推出了2个库,取代片段。 我会说这实际上可能比试图破解碎片进入做一些谷歌并不希望他们做一个更好的选择。

http://corner.squareup.com/2014/01/mortar-and-flow.html

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

我想我会忍受这种解决方案,以帮助谁在未来这个问题的人。 如果您跟踪通过与其他人的原始海报的对话,并期待在他发布的代码,你会看到原来的海报,最终涉及到使用无操作的动画对孩子片段,而动画父片段的结论。 因为它迫使你保持所有子片段,使用具有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;
}

}

接下来,你需要有一个扩展片段,您的所有片段延长(至少你的孩子碎片)的类。 我已经有这个类,我把它叫做BaseFragment。 当创建一个片段来看,我们把它添加到ChildFragmentAnimationManager,当它的破坏将其删除。 你可以这样做onAttach /断开,或序列中的其它匹配方法。 我的选择创建/破坏景观的逻辑是因为如果一个片段不有一个观点,我不关心其动画继续观察。 这种方法也适用于使用片段,你将不会被保留,一个FragmentPagerAdapter持有的每一个片段的轨道ViewPagers更好地工作,而只有3个。

public abstract class BaseFragment extends Fragment {

@Override
public  View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    Fragment parent = getParentFragment();
    if (parent != null) {
        ChildFragmentAnimationManager.instance().putChild(parent, this);
    }

    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onDestroyView() {
    Fragment parent = getParentFragment();
    if (parent != null) {
        ChildFragmentAnimationManager.instance().removeChild(parent, this);
    }

    super.onDestroyView();
}

}

现在,所有的片段由父片段存储在内存中,你可以叫动画上他们这个样子,和你的孩子碎片不会消失。

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 /动画文件夹中的文件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>

同样,我不认为这个方案是完美的,但它是比你有一个孩子片段每个实例好得多,在父片段实现自定义代码来跟踪每一个孩子的。 我去过那里,这是没有乐趣。



Answer 8:

我想我找到了一个更好的解决这个问题比快照当前片段的位图Luksprog建议。

关键是要隐藏被删除或分离的片段和动画都完成后,才片段被删除或在其自己的片段事务分离。

试想一下,我们有FragmentAFragmentB ,都与子片段。 现在,当你通常会做:

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .add(R.id.fragmentHolder, new FragmentB())
  .remove(fragmentA)    <-------------------------------------------
  .addToBackStack(null)
  .commit()

相反,你做

getSupportFragmentManager()
  .beginTransaction()
  .setCustomAnimations(anim1, anim2, anim1, anim2)
  .add(R.id.fragmentHolder, new FragmentB())
  .hide(fragmentA)    <---------------------------------------------
  .addToBackStack(null)
  .commit()

fragmentA.removeMe = true;

现在的片段实施:

public class BaseFragment extends Fragment {

    protected Boolean detachMe = false;
    protected Boolean removeMe = false;

    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        if (nextAnim == 0) {
            if (!enter) {
                onExit();
            }

            return null;
        }

        Animation animation = AnimationUtils.loadAnimation(getActivity(), nextAnim);
        assert animation != null;

        if (!enter) {
            animation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    onExit();
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                }
            });
        }

        return animation;
    }

    private void onExit() {
        if (!detachMe && !removeMe) {
            return;
        }

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        if (detachMe) {
            fragmentTransaction.detach(this);
            detachMe = false;
        } else if (removeMe) {
            fragmentTransaction.remove(this);
            removeMe = false;
        }
        fragmentTransaction.commit();
    }
}


Answer 9:

我在地图片段同样的问题。 它保留了其包含片段的出口动画过程中消失。 解决方法是添加动画为孩子地图碎片,将父片段的出口动画过程中保持可见。 子片段的动画在其持续时间期间保持其alpha在100%。

动画:RES /动画/ 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>

当在地图片段添加到父片段动画然后施加。

与母体部分

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

最后,孩子片段动画的持续时间在一个资源文件中设置。

值/ integers.xml

<resources>
  <integer name="keep_child_fragment_animation_duration">500</integer>
</resources>


Answer 10:

动画neasted片段dissapearance我们可以强制弹回堆栈ChildFragmentManager。 这将触发过渡动画。 要做到这一点,我们需要赶上OnBackButtonPressed事件或监听堆栈中的变化。

下面是例如用代码。

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();


Answer 11:

最近,我遇到了我的问题这个问题: 嵌套片段转换错误

我有解决这个不保存的位图,也没有使用反射或其他任何不满意的方法解决。

一个例子项目在这里可以查看: https://github.com/zafrani/NestedFragmentTransitions

的效果的GIF在这里可以查看: https://imgur.com/94AvrW4

在我的例子中,有6个孩子片段,两家母公司片段之间分配。 我能够实现进入,退出,流行音乐和推动的过渡没有任何问题。 配置更改和回压也成功地处理。

该解决方案的大部分是在我BaseFragment的onCreateAnimator功能看起来像这样(我的孩子和家长片段延长片段):

   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()
}

活动和家长片段负责制定这些布尔值的状态。 它更容易查看如何从我的例子项目中。

我没有使用支持片段在我的例子,但同样的逻辑可以与他们和他们的onCreateAnimation功能使用



Answer 12:

一个简单的方法来解决这个问题是使用Fragment类从这个库而不是标准的库片段类:

https://github.com/marksalpeter/contract-fragment

作为一个侧面说明,包装中还含有一种叫一个有用的委托模式ContractFragment ,你可能会发现构建您的应用程序充分利用亲子片段关系是有用的。



Answer 13:

从@kcoppock的上面的回答,

如果你有活动 - >片段 - >片段(多次叠加,下面的帮助),一个小修改,以最好的答案恕我直言。

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


Answer 14:

我的问题是关于父片段去除(ft.remove(片段)),儿童动画都没有发生。

最根本的问题是,子片段之前立即销毁,父母片段退出动画。

儿童片段自定义动画没有得到父母上去除片段执行

正如其他人躲避到,隐藏父(而不是孩子)是先于母公司去除的路要走。

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

如果你真的要删除你应该建立在你的监听器自定义动画当动画结束就知道父母,这样,那么你可以放心地做家长的一些片段定稿(删除)。 如果你不这样做,及时,你可能最终杀死动画。 NB动画自身的异步队列完成。

顺便说一句,你不需要对孩子的片段自定义动画,因为他们将继承父动画。



文章来源: Nested fragments disappear during transition animation