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

  • 45
    Like
  • 0
    Comment
More than 1 year has 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リソースで完結できるので検討の対象としたい。