嵌套片段转换不正确

时间:2017-10-21 01:04:31

标签: android android-fragments fragmenttransaction android-nested-fragment fragment-animation

你好堆栈溢出的程序员!我已经花了很长时间解决这个问题,现在非常绝望来寻求解决方案。

场景

我使用android.app.Fragment' s不要与支持片段混淆。

我有6个名为:

的子片段
  • FragmentOne
  • FragmentTwo
  • FragmentThree
  • FragmentA
  • FragmentB
  • FragmentC

我有两个父片段名为:

  • FragmentNumeric
  • FragmentAlpha

我有一个名为的活动:

  • MainActivity

他们的行为如下:

  • 子片段是仅显示视图的片段,它们不显示也不包含片段。
  • 父片段用一个子片段填充整个视图。他们能够用其他子片段替换子片段。
  • 活动用父片段填充其大部分视图。它可以用其他父片段替换它。 因此,在任何时候屏幕都只显示一个子片段。

你可能已经猜到了,

FragmentNumeric显示子片段FragmentOneFragmentTwoFragmentThree

FragmentAlpha显示子片段FragmentAFragmentBFragmentC

问题

我试图过渡/动画父母和子女片段。儿童片段按预期顺利过渡。但是,当我转换到新的父片段时,它看起来很糟糕。子片段看起来像是从其父片段运行独立的过渡。并且子片段看起来也像是从父片段中删除的。可以在这里查看它的gif https://imgur.com/kOAotvk。请注意单击Show Alpha时会发生什么。

最接近的问题&我能找到的答案在这里:Nested fragments disappear during transition animation然而所有的答案都令人不满意。

Animator XML Files

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

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没有输入效果,所以它总是准备好活动,并且没有退出效果,因为没有任何东西退出。我也在使用FragmentTransaction#add,而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

与快速显示其第一个子片段的活动相同。

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>

使用dagger和一些契约我让子片段回调到它们的父片段并通过执行以下操作来托管活动:

FragmentOne将按钮单击侦听器设置为:

(parentFragment as FragmentNumeric).showFragmentTwo()

FragmentTwo设置按钮点击监听器:

(parentFragment as FragmentNumeric).showFragmentThree()

FragmentThree不同,它会将click监听器设置为:

(activity as MainActivity).showFragmentAlpha()

有没有人能解决这个问题?

更新1

我已根据要求添加了一个示例项目:https://github.com/zafrani/NestedFragmentTransitions

它与原始视频中的差异是父片段不再使用具有xFraction属性的视图。因此看起来输入动画不再具有重叠效果。但它仍然会从父项中删除子片段并将它们并排放置。动画完成后,片段三将立即替换为片段A.

更新2

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

2 个答案:

答案 0 :(得分:4)

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

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

Parent和Child片段的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()
}

布鲁尔人正处于不同的情景中,在公关中更容易看到。

动画功能是:

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

如果有人找到更好的方法,请将问题保持开放。

答案 1 :(得分:0)

您正在获得如此有线的结果,因为您正在使用片段的退出动画。基本上,我们每次都有片段动画的问题,最后从源片段动画转移到使用自己,每次我们预先形成过渡。

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

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

2)另一个选项,可能是套件,它考虑应用程序结构并尽量避免用动画替换Fragment。如果你需要替换,不要使用动画,你可以添加任何动画(包括进入和退出),但只能添加。