Edited at

LollipopでFragmentをスライドインするアニメーションの適用

More than 3 years have passed since last update.


LollipopでFragmentのスライドインアニメーション時の問題

いくつかのインストラクションを連続で表示させる場合など、複数のFragmentを順にスライドインさせたり、またはバックキーでスライドアウトさせるUIはFragmentTransaction#setCustomAnimationsを利用し、その際、下記のようなアニメーションリソースを指定することで実装できる。


slide_in_left.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">

<translate
android:fromXDelta="-100%" android:toXDelta="0%"
android:duration="500"/>
</set>

さらに簡易に実装しようとすると、Android側で用意されたリソースを利用し、

FragmentManager fm = getFragmentManager();

FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right);

しかし、Target Sdk VersionをLollipopにあげた場合、java.lang.RuntimeException: Unknown animator name: translateが発生した。


ObjectAnimator指定の問題

インターネットで検索すると、FragmentTransaction#setCustomAnimationsはObjectAnimatorを利用することで上記の問題を回避できるようだ。


slide_in_left.xml

<set xmlns:android="http://schemas.android.com/apk/res/android" >

<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:propertyName="x"
android:valueFrom="1000"
android:valueTo="0"
android:valueType="floatType" />
</set>


ただし、上記コードを見てわかる通り、valueFromにパーセントの指定ができない(参考)。本来であればFragmentがAddされているコンテナのViewの幅を指定したいが、xmlリソースからはそれができないようだ。


onCreateAnimatorによる解決

解決方法はFragment#onCreateAnimatorでコード上で動的にスライドアニメーションを作る。その際、Containerの幅を保存しておき、その値を元にアニメーションを作成する。


HogeFragment.java

public class HogeFragment extends Fragment {

private int containerId;

private int containerWidth;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
containerId = container.getId();
//***コンテナの幅を保存しておく***
containerWidth = container.getWidth();

// 以下、省略 //
}

@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
//***FragmentTransactionにTRANSIT_FRAGMENT_OPENを指定しておくと、遷移時にはTRANSIT_FRAGMENT_OPEN、Back時にはTRANSIT_FRAGMENT_CLOSEが渡される***
if (transit == FragmentTransaction.TRANSIT_FRAGMENT_OPEN) {
if (enter) {
return ObjectAnimator.ofFloat(getView(), "x", containerWidth, 0.0f);
} else {
return ObjectAnimator.ofFloat(getView(), "x", 0.0f, -containerWidth);
}
} else if (transit == FragmentTransaction.TRANSIT_FRAGMENT_CLOSE) {
if (enter) {
return ObjectAnimator.ofFloat(getView(), "x", -containerWidth, 0.0f);
} else {
return ObjectAnimator.ofFloat(getView(), "x", 0.0f, containerWidth);
}
}

return super.onCreateAnimator(transit, enter, nextAnim);
}

private void showNextFragment() {
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
//***TRANSIT_FRAGMENT_OPENを指定する***
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
Fragment fragment = NextFragment.newInstance();
ft.replace(containerId, fragment);
ft.addToBackStack(null);
ft.commit();
}
}


また、例えば1枚目のインストラクションページはスライドインアニメーションを行いたくないときは、if (transit == FragmentTransaction.TRANSIT_FRAGMENT_OPEN) {ブロックを削除すれば良い。


おまけ

トランスレーションするアニメーション以外を使う選択肢もある。例えば、カードがフリップするアニメーションだとObjectAnimatorのxmlリソースで完結できるので検討の対象としたい。