2012-10-22 72 views
55

我想要使用FragmentTransaction.setCustomAnimations实现以下效果。FragmentTransaction动画滑过顶部

  1. 片段A是显示
  2. 替换片段A与片段B片段A的更换期间应保持可见。片段B应该从右侧滑入。片段B应该在片段A的顶部滑动。

我没有问题在动画设置中获取幻灯片。我的问题是,我无法弄清楚如何使片段A保持原状,并且在动画中的幻灯片正在运行时处于片段B下。无论我做什么,似乎碎片A都是顶级的。

我该如何做到这一点?

这里是FragmentTransaction代码:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.nothing, R.anim.nothing, 
    R.anim.slide_out_right); 
ft.replace(R.id.fragment_content, fragment, name); 
ft.addToBackStack(name); 
ft.commit(); 

正如你可以看到我已经定义了一个动画R.anim.nothing为“走出去”的动画,因为我其实不想片段A做任何其他而不仅仅是在交易过程中保持它的位置。

这里是动画资源:

slide_in_right.xml

<translate xmlns:android="http://schemas.android.com/apk/res/android" 
    android:duration="@android:integer/config_mediumAnimTime" 
    android:fromXDelta="100%p" 
    android:toXDelta="0" 
    android:zAdjustment="top" /> 

nothing.xml

<alpha xmlns:android="http://schemas.android.com/apk/res/android" 
    android:duration="@android:integer/config_mediumAnimTime" 
    android:fromAlpha="1.0" 
    android:toAlpha="1.0" 
    android:zAdjustment="bottom" /> 
+1

我已经提交了一个错误报告,因为当前的Z排序是错误的。 https://code.google.com/p/android/issues/detail?id=163384&thanks=163384&ts=1428443402 – sbaar

回答

9

我发现这对我的作品的解决方案。我最终使用带有FragmentStatePagerAdapter的ViewPager。 ViewPager提供划动行为和片段中的FragmentStatePagerAdapter交换。实现使页面在传入页面下可见的效果的最后一招是使用PageTransformer。 PageTransformer重写ViewPager在页面之间的默认转换。下面是一个PageTransformer的例子,它可以在左侧页面上实现翻译和少量缩放的效果。

public class ScalePageTransformer implements PageTransformer { 
    private static final float SCALE_FACTOR = 0.95f; 

    private final ViewPager mViewPager; 

    public ScalePageTransformer(ViewPager viewPager) { 
      this.mViewPager = viewPager; 
    } 

    @SuppressLint("NewApi") 
    @Override 
    public void transformPage(View page, float position) { 
     if (position <= 0) { 
      // apply zoom effect and offset translation only for pages to 
      // the left 
      final float transformValue = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR) + SCALE_FACTOR; 
      int pageWidth = mViewPager.getWidth(); 
      final float translateValue = position * -pageWidth; 
      page.setScaleX(transformValue); 
      page.setScaleY(transformValue); 
      if (translateValue > -pageWidth) { 
       page.setTranslationX(translateValue); 
      } else { 
       page.setTranslationX(0); 
      } 
     } 
    } 

} 
+0

伟大的解决方案,但不幸的是,如果你想改变堆栈,不适合,特别是在减少方向 - 它会导致眩光(重绘整个元素)。 –

20

我不知道,如果你还需要一个答案,但我最近需要做同样的事情,我找到了一种方法去做你想做的事。

我做了这样的事情:

FragmentManager fm = getFragmentManager(); 
FragmentTransaction ft = fm.beginTransaction(); 

MyFragment next = getMyFragment(); 

ft.add(R.id.MyLayout,next); 
ft.setCustomAnimations(R.anim.slide_in_right,0); 
ft.show(next); 
ft.commit(); 

我在一个FrameLayout里显示我的片段。

它工作的罚款,但旧的片段仍然在我看来,我让像他希望,因为如果我把机器人进行管理:

ft.remove(myolderFrag); 

它不是在动画过程中显示出来。

slide_in_right.xml

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android" > 
<translate android:duration="150" android:fromXDelta="100%p" 
android:interpolator="@android:anim/linear_interpolator" 
android:toXDelta="0" /> 
</set> 
+0

谢谢,我仍在寻找解决方案。你的答案是准确的,但不幸的是它并没有完全解决问题。我不能将旧片段留在记忆中,否则会吃掉过多的记忆。我采取了从传出视图创建位图的方法,并将其放在新片段添加到的视图下。这实现了效果,但我又遇到了内存问题,因为位图太大。我正在研究其他一些方法,包括ViewPager w/FragmentStatePagerAdapter和每个Fragment的一个FrameLayout。稍后再发布。 –

+1

@ domji84 <?xml version =“1.0”encoding =“utf-8”?> 机器人:持续时间= “150” 机器人:fromXDelta = “100%p” 机器人:内插器= “@机器人:动画/ linear_interpolator” 机器人:toXDelta = “0”/> ' – Meph

+0

感谢。 'overridePendingTransition(R.anim.my_animation,0)'是我正在寻找的行。 –

2

我发现了一个替代的解决方案(未大量测试),我发现比迄今建议更优雅:

final IncomingFragment newFrag = new IncomingFragment(); 
newFrag.setEnterAnimationListener(new Animation.AnimationListener() { 
       @Override 
       public void onAnimationStart(Animation animation) { 

       } 

       @Override 
       public void onAnimationEnd(Animation animation) { 
        clearSelection(); 
        inFrag.clearEnterAnimationListener(); 

        getFragmentManager().beginTransaction().remove(OutgoingFragment.this).commit(); 
       } 

       @Override 
       public void onAnimationRepeat(Animation animation) { 

       } 
      }); 

getActivity().getSupportFragmentManager().beginTransaction() 
        .setCustomAnimations(R.anim.slide_in_from_right, 0) 
        .add(R.id.container, inFrag) 
        .addToBackStack(null) 
        .commit(); 

这是正在从内类OutgoingFragment类中调用。

正在插入新片段,动画完成,然后旧片段被删除。

在某些应用程序中可能存在一些内存问题,但它比无限期保留这两个片段要好。

+0

对我而言,这是迄今为止最好的解决方案。我曾经这样做过,但在动画过程中用一个Handler暂停系统,然后删除之前的片段,但这样做会好很多。 – JDenais

+0

IncomingFragment和OutgoingFragment类从哪里来?他们是否定制?不完全理解这背后的实现。也许在编写时有'Fragment.setEnterAnimationListener(...)'这样的函数......或者是那个片段的自定义函数? – NineToeNerd

4

经过更多的实验(因此这是我的第二个答案),问题似乎是,当我们想要另一个动画明确地说'保持放置'时,R.anim.nothing就意味着'消失'。解决的办法是这样定义一个真正的“什么都不做”的动画:

Make文件no_animation.xml

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android"> 
    <scale 
     android:interpolator="@android:anim/linear_interpolator" 
     android:fromXScale="1.0" 
     android:toXScale="1.0" 
     android:fromYScale="1.0" 
     android:toYScale="1.0" 
     android:duration="200" 
     /> 
    <alpha xmlns:android="http://schemas.android.com/apk/res/android" 
     android:fromAlpha="1.0" 
     android:toAlpha="0.0" 
     android:duration="200" 
     android:startOffset="200" 
     /> 
</set> 

现在简单地做,你会以其他方式:

getActivity().getSupportFragmentManager().beginTransaction() 
       .setCustomAnimations(R.anim.slide_in_right, R.anim.no_animation) 
       .replace(R.id.container, inFrag, FRAGMENT_TAG) 
       .addToBackStack("Some text") 
       .commit(); 
+0

我会尝试这个,但是我做了一个合适的“stay_put”动画,它所做的只是覆盖新片段的滑动动画,而旧片段停留在前景上。但我看到你抵消了no_animation的开始,所以也许它的确有窍门。我会让你知道我对你的解决方案的看法。 – JDenais

+1

不错的想法,但这不是作为交易立即删除片段,我可以看到动画过程中第二个片段下方的白色屏幕。 –

+0

困惑,为什么你认为它需要这个。我只用了0,我不想过渡,并且效果很好。这看起来像你在上面做的。 – NineToeNerd

11

从棒棒糖开始,你可以增加您输入片段的de translationZ。它会出现在现有的上面。

例如:

@Override 
public void onViewCreated(View view, Bundle savedInstanceState) { 
    super.onViewCreated(view, savedInstanceState); 
    ViewCompat.setTranslationZ(getView(), 100.f); 
} 

如果你想只为动画的持续时间修改translationZ值,你应该做这样的事情:

@Override 
public Animation onCreateAnimation(int transit, final boolean enter, int nextAnim) { 
    Animation nextAnimation = AnimationUtils.loadAnimation(getContext(), nextAnim); 
    nextAnimation.setAnimationListener(new Animation.AnimationListener() { 

     private float mOldTranslationZ; 

     @Override 
     public void onAnimationStart(Animation animation) { 
      if (getView() != null && enter) { 
       mOldTranslationZ = ViewCompat.getTranslationZ(getView()); 
       ViewCompat.setTranslationZ(getView(), 100.f); 
      } 
     } 

     @Override 
     public void onAnimationEnd(Animation animation) { 
      if (getView() != null && enter) { 
       ViewCompat.setTranslationZ(getView(), mOldTranslationZ); 
      } 
     } 

     @Override 
     public void onAnimationRepeat(Animation animation) { 
     } 
    }); 
    return nextAnimation; 
} 
+0

找到这个很好的工作,很早以前就在寻找这个解决方案,现在它已经解决了。 –

+0

谢谢。似乎工作。我可以知道为什么重写'onCreateAnimation',我们需要通过'AnimationUtils.loadAnimation'手动构建动画吗?我意识到如果我试图通过super.onCreateAnimation获取'Animation',它是空的。 –

+0

@CheokYanCheng因为'Fragment.onCreateAnimation()'方法默认不做任何事情,只是返回null。这种方法就在那里给你创建动画的可能性。 如果您查看方法'FragmentManager.loadAnimation()',您会看到它首先尝试从'fragment.onCreateAnimation()'获取动画,如果它返回null,则FragmentManager会自动创建动画。 – JFrite

0
FragmentTransaction ft = ((AppCompatActivity) context).getSupportFragmentManager().beginTransaction(); 
       ft.setCustomAnimations(0, R.anim.slide_out_to_right); 
      if (!fragment.isAdded()) 
      { 
       ft.add(R.id.fragmentContainerFrameMyOrders, fragment); 
       ft.show(fragment); 
      } 
      else 
       ft.replace(R.id.fragmentContainerFrameMyOrders, fragment); 
      ft.commit(); 
0

这是我的目前的解决方法对任何感兴趣的人。

在功能上添加新的Fragment

final Fragment toRemove = fragmentManager.findFragmentById(containerID); 
if (toRemove != null) { 
    new Handler().postDelayed(new Runnable() { 
      @Override 
      public void run() { 
       fragmentManager.beginTransaction().hide(toRemove).commit(); 
      } 
     }, 
     getResources().getInteger(android.R.integer.config_mediumAnimTime) + 100); 
     // Use whatever duration you chose for your animation for this handler 
     // I added an extra 100 ms because the first transaction wasn't always 
     // fast enough 
} 
fragmentManager.beginTransaction() 
    .setCustomAnimations(enter, 0, 0, popExit).add(containerID, fragmentToAdd) 
    .addToBackStack(tag).commit(); 

onCreate

final FragmentManager fragmentManager = getSupportFragmentManager(); 
fragmentManager.addOnBackStackChangedListener(
     new FragmentManager.OnBackStackChangedListener() { 
      @Override 
      public void onBackStackChanged() { 
       Fragment current = fragmentManager.findFragmentById(containerID); 
       if (current != null && current.isHidden()) { 
        fragmentManager.beginTransaction().show(current).commit(); 
       } 
      } 
     }); 

我宁愿某种AnimationListener代替上述处理程序,但我没看到您可以使用任何方式来检测事务动画的结尾,而这些动画与onCreateAnimation()之类的片段无关。任何建议/编辑与适当的倾听者将不胜感激。

我会指出Fragment s我以这种方式添加的是轻量级的,所以对于我来说,将它们放在片段容器中以及它们所在的片段上并不是问题。

如果你想删除你可以把fragmentManager.beginTransaction().remove(toRemove).commitAllowingStateLoss();HandlerRunnable的片段,并在OnBackStackChangedListener

// Use back stack entry tag to get the fragment 
Fragment current = getCurrentFragment(); 
if (current != null && !current.isAdded()) { 
    fragmentManager.beginTransaction() 
     .add(containerId, current, current.getTag()) 
     .commitNowAllowingStateLoss(); 
} 

注意上面的解决方案并不在容器中的第一个分片工作(因为它不在后面的堆栈中),所以你将不得不有另一种方法来恢复那个,也许保存对第一个片段的引用......但是如果你不使用后端堆栈并且总是手动替换片段这不是问题。或者,您可以将所有片段添加到后端堆栈(包括第一个片段)并覆盖onBackPressed,以确保您的活动退出,而不是在后端堆栈中只剩下一个片段时显示空白屏幕。

编辑: 我发现下面的函数,很可能取代FragmentTransaction.remove()FragmentTransaction.add()以上:

FragmentTransactiondetach()

从UI中分离给定片段。这与放置在后端堆栈上的状态相同:片段已从UI中删除,但其状态仍由片段管理器主动管理。进入这种状态时,它的视图层次被破坏。

FragmentTransactionattach()

重新附加一个片段,它之前已通过detach(Fragment)从UI分离。这会导致其视图层次被重新创建,附加到UI并显示。