在转换animation过程中,嵌套的碎片消失
这里是场景:活动包含片段A
,它依次使用getChildFragmentManager()
在其onCreate
添加片段A1
和A2
,如下所示:
getChildFragmentManager() .beginTransaction() .replace(R.id.fragmentOneHolder, new FragmentA1()) .replace(R.id.fragmentTwoHolder, new FragmentA2()) .commit()
到目前为止,这么好,一切都按预期运行。
然后,我们在Activity中运行以下事务:
getSupportFragmentManager() .beginTransaction() .setCustomAnimations(anim1, anim2, anim1, anim2) .replace(R.id.fragmentHolder, new FragmentB()) .addToBackStack(null) .commit()
在转换过程中,片段B
的enter
animation正确运行,但片段A1和A2完全消失 。 当我们用Backbutton恢复事务时,它们在popEnter
animation期间正确初始化并正常显示。
在我的简短testing中,它得到了更多的 – 如果我设置了子片段的animation(见下文),当我们添加片段B
时, exit
animation间歇运行
getChildFragmentManager() .beginTransaction() .setCustomAnimations(enter, exit) .replace(R.id.fragmentOneHolder, new FragmentA1()) .replace(R.id.fragmentTwoHolder, new FragmentA2()) .commit()
我想实现的效果很简单 – 我想要片段A
(anim2)上的exit
(或者应该是popExit
?)animation来运行整个容器,包括其嵌套的子项。
有什么办法可以实现吗?
编辑 :请在这里find一个testing用例
编辑2 :感谢@StevenByle推动我继续尝试与静态animation。 显然你可以在每个操作的基础上设置animation(对整个事务不是全局的),这意味着孩子可以有一个无限的静态animation集,而他们的父母可以有不同的animation,整个事情可以在一个事务中提交。 请参阅下面的讨论和更新的testing用例项目 。
为了避免用户在事务中删除/replace父片段时看到嵌套片段消失,您可以通过提供它们的图像来“模拟”仍然存在的片段,就像它们出现在屏幕上一样。 这个图像将被用作嵌套片段容器的背景,所以即使嵌套片段的视图消失,图像也将模拟它们的存在。 另外,我没有看到与嵌套的片段的视图的交互作为一个问题,因为我不认为你会希望用户在他们正在被删除的过程中(可能作为一个用户行为好)。
我已经做了一个小例子 ,设置背景图像(基本的东西)。
所以似乎有很多不同的解决方法,但是基于@ Jayd16的回答,我想我已经find了一个非常稳固的通用解决scheme,它仍然允许在子片段上定制过渡animation,而且不需要做布局的位图caching。
有一个扩展Fragment
的BaseFragment
类,并使所有片段扩展该类(而不仅仅是子片段)。
在该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; } }
不幸的是,它确实需要反思; 但是,由于此解决方法适用于支持库,因此除非更新支持库,否则不会承担底层实现更改的风险。 如果您从源代码构build支持库,则可以将下一个animation资源ID的访问者添加到Fragment.java
并删除reflection。
这个解决scheme不需要“猜测”父母的animation持续时间(这样“无所事事”的animation将具有与父母的退出animation相同的持续时间),并且允许您仍然对子片段执行自定义animation(例如,用不同的animation交换儿童片段)。
我能够拿出一个相当干净的解决scheme。 海事组织是最不起眼的,虽然这在技术上是“绘制一个位图”的解决scheme,至less它的碎片库的抽象。
确保你的孩子使用以下代码覆盖父类:
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); }
如果我们在子碎片上有一个退出animation,它们将变成animation,而不是闪烁。 我们可以通过一个简单的animation来完成这个animation,这个animation只需要以完整的alpha值绘制子片段一段时间。 这样,它们就会在animation中保持在父片段中可见,从而给出所需的行为。
我能想到的唯一问题就是跟踪这段时间。 我可以将它设置为一个大数字,但是如果它仍然在某处绘制该animation,恐怕会出现性能问题。
我张贴我的解决scheme清晰。 解决scheme非常简单。 如果您试图模仿父级的片段事务animation,只需将自定义animation添加到具有相同持续时间的子片段事务中即可。 哦,并确保您在添加()之前设置自定义animation。
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(我的父母进入/退出animation时间是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>
我知道这可能无法完全解决你的问题,但也许它会适合别人的需求,你可以添加enter
/ exit
和popEnter
/ popExit
animation到你的孩子Fragment
s实际上没有移动/animationFragment
s。 只要animation具有与其父Fragment
animation相同的持续时间/偏移量,它们将显示为随父animation一起移动/animation。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@
编辑:我结束了实现这个解决scheme,因为有其他问题,这有。 Square最近推出了2个replace碎片的库。 我认为这可能是一个更好的select,比试图破解谷歌不希望他们做的事情。
http://corner.squareup.com/2014/01/mortar-and-flow.html
@@@@@@@@@@@@@@@@@@@@@@@@@@@@
我想我会提出这个解决scheme来帮助那些将来有这个问题的人。 如果您通过与其他人的原始海报对话追踪,并查看他发布的代码,您将看到原始的海报最终得出结论,在对父片段进行animation处理时,对子片段使用不操作的animation。 这个解决scheme并不理想,因为它迫使你跟踪所有的子片段,当使用带有FragmentPagerAdapter的ViewPager时,这可能会很麻烦。
由于我使用了儿童碎片,所以我想出了这个解决scheme,它是高效的,模块化的(所以它可以很容易地移除),以防万一他们修复它,这个不需要操作的animation不再需要。
有很多方法可以实现这一点。 我select使用一个单身人士,我称之为ChildFragmentAnimationManager。 基本上,它会根据父母跟踪我的孩子片段,并在被问及时向孩子申请一个不操作的animation。
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; } }
接下来,你需要有一个扩展Fragment的类来扩展你所有的片段(至less是你的子片段)。 我已经有了这个类,我称之为BaseFragment。 在创build片段视图时,我们将其添加到ChildFragmentAnimationManager中,并在其被销毁时将其删除。 你可以在连续/分离或序列中的其他匹配方法。 我select创build/销毁视图的逻辑是因为如果一个片段没有视图,我不在乎animation它继续被看到。 这个方法对于使用Fragments的ViewPagers也应该更好,因为你不会跟踪FragmentPagerAdapter持有的每一个Fragment,而只是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(); } }
现在所有的片段都被父片段存储在内存中,您可以像这样调用它们的animation,并且您的子片段不会消失。
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>
再一次,我不认为这个解决scheme是完美的,但是比每个你有一个子片段的实例要好得多,在父片段中实现自定义代码来跟踪每个孩子。 我去过那里,而且没有什么乐趣。
我遇到了与地图片段相同的问题。 它在包含片段的退出animation期间一直消失。 解决方法是为子映射片段添加animation,该animation将在父片段的退出animation期间保持可见。 子片段的animation在其持续时间内保持其alpha为100%。
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>
然后在地图片段添加到父片段时应用animation。
父母片段
@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; }
最后,子片段animation的持续时间在资源文件中设置。
值/ integers.xml
<resources> <integer name="keep_child_fragment_animation_duration">500</integer> </resources>
我觉得我find了一个更好的解决scheme,而不是像Luksprogbuild议的那样将当前片段快照到一个位图。
诀窍是隐藏要移除或分离的片段,并且只有在完成animation之后,片段才会在其自己的片段事务中被移除或分离。
想象一下,我们有FragmentA
和FragmentB
,都有子片段。 现在当你通常做:
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(); } }
为了消除内联片段的缺点,我们可以强制在ChildFragmentManager上popup堆栈。 这将触发过渡animation。 要做到这一点,我们需要赶上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();
我最近碰到这个问题在我的问题: 嵌套片段转换不正确
我有一个解决scheme,解决这个问题,而不保存位图,也没有使用reflection或任何其他令人不满意的方法。
一个示例项目可以在这里查看: https : //github.com/zafrani/NestedFragmentTransitions
效果的GIF可以在这里查看: https : //imgur.com/94AvrW4
在我的例子中,有6个子片段,分成两个父片段。 我能够在没有任何问题的情况下实现进入,退出,popup和推送的转换。 configuration更改和后退处理也成功处理。
该解决scheme的大部分是在我的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函数