2012-10-22 75 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並顯示。