17

你好程序员的堆栈溢出!我已经花了一周的时间解决了这个问题,现在我非常想要绝望嵌套片段转换不正确


场景

我使用android.app.Fragment不与载体片段相混淆。

我有6个孩子命名片段:

  • FragmentOne
  • FragmentTwo
  • FragmentThree
  • FragmentA
  • FragmentB
  • FragmentC

我已2母体片段命名为:

  • FragmentNumeric
  • FragmentAlpha

我已经1个活性命名为:

  • MainActivity

他们的表现在以下几点:

  • 儿童片段片段只显示一个视图,他们不显示,也不包含片段。
  • 父级片段使用单个子片段填充其整个视图。他们能够将子片段替换为其他子片段。
  • 该活动使用父级片段填充其大部分视图。它可以将其替换为其他父代片段。 因此,在任何时候屏幕只显示一个孩子片段。

正如你可能已经猜到,

FragmentNumeric显示子片段FragmentOneFragmentTwoFragmentThree

FragmentAlpha显示子片段FragmentA,FragmentBFragmentC


我试图转型/动画家长和孩子碎片问题。如预期的那样,孩子的片段顺利过渡。但是当我转换到一个新的父代片段时,它看起来很糟糕。子片段看起来像从其父片段运行独立转换。子片段看起来也是从父片段中移除的。它的一个GIF可以在这里查看https://imgur.com/kOAotvk。注意当我点击Show Alpha时会发生什么。

最接近的问题&答案我可以在这里找到:Nested fragments disappear during transition animation但是,所有的答案都令人不满。


动画XML文件

我有以下的动画效果(持续时间长用于测试目的):

fragment_enter.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" 
     android:interpolator="@android:anim/linear_interpolator" 
     android:propertyName="xFraction" 
     android:valueFrom="1.0" 
     android:valueTo="0" /> 
</set> 

fragment_exit.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" 
     android:interpolator="@android:anim/linear_interpolator" 
     android:propertyName="xFraction" 
     android:valueFrom="0" 
     android:valueTo="-1.0" /> 
</set> 

fragment_pop.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" 
     android:interpolator="@android:anim/linear_interpolator" 
     android:propertyName="xFraction" 
     android:valueFrom="0" 
     android:valueTo="1.0" /> 
</set> 

fragment_push.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" 
     android:interpolator="@android:anim/linear_interpolator" 
     android:propertyName="xFraction" 
     android:valueFrom="-1.0" 
     android:valueTo="0" /> 
</set> 

fragment_nothing.xml

<?xml version="1.0" encoding="utf-8"?> 
<set> 
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" 
     android:duration="4000" /> 
</set> 

MainActivity.kt

需要考虑的事情:第一父片段,FragmentNumeric,没有进入影响所以它总是准备好与活动,并没有退出的影响,因为没有什么是退出。其中,作为FragmentAlpha在迅速显示出它的第一个孩子片段条款使用FragmentTransaction#replace

class MainActivity : AppCompatActivity { 

    fun showFragmentNumeric(){ 
     this.fragmentManager.beginTransaction() 
       .setCustomAnimations(R.animator.fragment_nothing, 
            R.animator.fragment_nothing, 
            R.animator.fragment_push, 
            R.animator.fragment_pop) 
       .add(this.contentId, FragmentNumeric(), "FragmentNumeric") 
       .addToBackStack("FragmentNumeric") 
       .commit() 
} 

    fun showFragmentAlpha(){ 
     this.fragmentManager.beginTransaction() 
       .setCustomAnimations(R.animator.fragment_enter, 
            R.animator.fragment_exit, 
            R.animator.fragment_push, 
            R.animator.fragment_pop) 
       .replace(this.contentId, FragmentAlpha(), "FragmentAlpha") 
       .addToBackStack("FragmentAlpha") 
       .commit() 
    } 

    override fun onCreate(savedInstanceState: Bundle?) { 
     super.onCreate(savedInstanceState) 
     if (savedInstanceState == null) { 
      showFragmentNumeric() 
     } 
    } 
} 

FragmentNumeric

做同样的事情作为活动我还使用FragmentTransaction#add它。

class FragmentNumeric : Fragment { 

    fun showFragmentOne(){ 
      this.childFragmentManager.beginTransaction() 
        .setCustomAnimations(R.animator.fragment_nothing, 
             R.animator.fragment_nothing, 
             R.animator.fragment_push, 
             R.animator.fragment_pop) 
        .add(this.contentId, FragmentOne(), "FragmentOne") 
        .addToBackStack("FragmentOne") 
        .commit() 
    } 

    fun showFragmentTwo(){ 
     this.childFragmentManager.beginTransaction() 
       .setCustomAnimations(R.animator.fragment_enter, 
            R.animator.fragment_exit, 
            R.animator.fragment_push, 
            R.animator.fragment_pop) 
       .replace(this.contentId, FragmentTwo(), "FragmentTwo") 
       .addToBackStack("FragmentTwo") 
       .commit() 
    } 


    fun showFragmentThree(){ 
     this.childFragmentManager.beginTransaction() 
       .setCustomAnimations(R.animator.fragment_enter, 
            R.animator.fragment_exit, 
            R.animator.fragment_push, 
            R.animator.fragment_pop) 
       .replace(this.contentId, FragmentThree(), "FragmentThree") 
       .addToBackStack("FragmentThree") 
       .commit() 
    } 

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { 
     super.onViewCreated(view, savedInstanceState) 
     if (savedInstanceState == null) { 
      if (this.childFragmentManager.backStackEntryCount <= 1) { 
       showFragmentOne() 
      } 
     } 
    } 
} 

其它片段

FragmentAlpha跟随相同的图案FragmentNumeric,用片段A,B和C.替换片段一,二及三。

儿童片段只是显示以下XML视图并动态设置其文本和按钮单击侦听器,以从父片段或活动调用函数。

view_child_example。XML

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:background="@color/background" 
    android:clickable="true" 
    android:focusable="true" 
    android:orientation="vertical"> 

    <TextView 
     android:id="@+id/view_child_example_header" 
     style="@style/Header" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" /> 


    <Button 
     android:id="@+id/view_child_example_button" 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" /> 
</LinearLayout> 

使用匕首和一些合同我有孩子片段回调到它们的父片段,并举办活动,通过执行类似下面:

FragmentOne设置按钮,点击监听,这样做:

(parentFragment as FragmentNumeric).showFragmentTwo() 

FragmentTwo设置按钮,点击监听,这样做:

(parentFragment as FragmentNumeric).showFragmentThree() 

FragmentThree不同的是,它会设置的点击收听做:

(activity as MainActivity).showFragmentAlpha() 

有没有人有这个问题的解决方案?


更新1

我添加了一个例子项目的要求:https://github.com/zafrani/NestedFragmentTransitions

从一个在我的原始视频在它和差变成父片段不再使用视图与xFraction属性。所以看起来输入动画不再有重叠效果。但它仍然会从父级删除子级片段并将它们并排设置。动画完成后,Fragment Three即刻替换为Fragment A。

更新2

父级和子级片段视图都使用xFraction属性。关键是在父母动画时抑制孩子动画。

+3

这个问题已经非常明确,但由于您似乎有一个干净的项目来显示问题,如果您可以将它上载到某个位置,它将会很酷。 – natario

+0

请更新GIF,看起来不见了。 UPD。对不起,我的坏,找到了图像。 – GensaGames

+0

@natario我已经添加了一个github链接。 – zafrani

回答

4

我想我已经找到了一种方法来解决这个使用Fragment#onCreateAnimator。转换的gif可以在这里查看:https://imgur.com/94AvrW4

我做了一个PR测试,到目前为止它的工作方式与我预期的一样,并且仍然存在配置更改并支持后退按钮。这里是链接https://github.com/zafrani/NestedFragmentTransitions/pull/1/files#diff-c120dd82b93c862b01c2548bdcafcb20R25

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

的布尔应用于不同场景,更易于在PR看到被设置。

动画功能是:

private fun enterAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_enter) 
    } 

    private fun exitAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_exit) 
    } 

    private fun pushAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_push) 
    } 

    private fun popAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_pop) 
    } 

    private fun nothingAnim(): Animator { 
     return AnimatorInflater.loadAnimator(activity, R.animator.fragment_nothing) 
    } 

将离开的问题开放柜面有人发现了一个更好的办法。

0

你得到如此有线的结果,因为你正在使用你的片段退出动画。基本上,我们每次都有Fragment动画的问题,并最终从源片段动画转移到使用自己,每次我们执行转换时。

1)要验证此行为,您可以删除片段的退出动画,并且一切都会正常。在大多数情况下,它应该是足够的,原因退出动画非常具体,只为单个片断管理使用(不以你的情况下,与孩子的)

getFragmentManager().beginTransaction() 
       .setCustomAnimations(R.animator.enter_anim_frag1, 
            0, 
            R.animator.enter_anim_frag2, 
            0) 
       .replace(xxx, Xxx1, Xxx2) 
       .addToBackStack(null) 
       .commit() 

2)另一种选择,这可能适合,这是考虑应用程序的结构并尽量避免用动画替换片段。如果您需要替换,请勿使用动画,并且您可以添加任何动画(包括进入和退出),但仅限于添加。