Activity/Fragment Transitionsのつかいかた

Android Advent Calendar 2014 21日目の記事です。

LollipopでActivity Transitionsが追加されました。(https://developer.android.com/training/material/animations.html#Transitions)
Fragmentにも無いのかな？と探してみたところ普通にありましたので,あわせて新たに追加されたTransitionの使い方をまとめます。


#できるようになったこと
今まではActivity/Fragmentの遷移時のアニメーションについては、それぞれの持つView **全体** に対して効果を指定していました。
LollipopからはTransitionを用いることにより、Activity/Fragmentの **各View** についてアニメーション可能になりました。
例えばViewが画面外に発散したり、Viewを共有させたまま画面遷移するといったアニメーションです。

#Transition
`android.transition`はKitKatにて追加されたパッケージで、UIの異なる2つの状態について遷移アニメーションを自動的に行ってくれます。
以下では用意されているもののうち6つを紹介します。
##Expode
収束・発散します
![explode.gif](https://qiita-image-store.s3.amazonaws.com/0/2743/12451627-2422-8910-1e0b-a05bf1feea7a.gif "explode.gif")

##Fade
フェードイン・フェードアウトします。
![fade.gif](https://qiita-image-store.s3.amazonaws.com/0/2743/d929691d-f6aa-bf85-5d67-9f37e639e99e.gif "fade.gif")

##Slide
スライドイン・スライドアウトします。
`setSlideEdge(int slideEdge)`でスライドする方向が指定出来ます。

```java:Slide
Slide slide = new Slide();
slide.setSlideEdge(Gravity.RIGHT);
```
![slide.gif](https://qiita-image-store.s3.amazonaws.com/0/2743/ddecb5cc-56eb-2bd0-3f80-0899e41a5c39.gif "slide.gif")

##ChangeBounds
Viewの位置・大きさが変わるようなTransitionです。
##ChangeImageTransform
ImageViewのmatrixが変化するようなTransitionです。
`ChangeBounds`と一緒に使うことで位置・大きさ・スケールがスムーズに変化します。
![image.gif](https://qiita-image-store.s3.amazonaws.com/0/2743/531be7b4-fb02-ad00-9f70-dce6d5a8a4a0.gif "image.gif")

##TransitionSet
他のTransitionをまとめます。`setOrdering(int ordering)`にて順番に処理するか同時に処理するかを設定出来ます。

```java:Transition
TransitionSet set = new TransitionSet();
set.addTransition(new ChangeBounds());
set.addTransition(new ChangeImageTransform());
//順番にTransitionを実行する
set.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
```





# Activity/Fragment Transitions実装の流れ
TransicionをActivityやFragmentの遷移に使うためには、以下のように記述します。

1. window contentのTransitionの有効化
2. Transitionの定義
3. Activity/FragmentにTransitionの設定
4. SharedElementの設定
5. Activity/Fragmentの起動

ここからはFragment/Activityそれぞれについて使い方を説明していきます。
## Fragment Trnasitions

###1. window contentのTransitionの有効化
コードまたはxmlでwindow contentのTransitionを有効にします。

```java:
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
```

```xml:style.xml
<resources>
    <style name="Theme.Base" parent="android:Theme.Material.Light">
        <item name="android:windowContentTransitions">true</item>
    </style>
</resources>
```
###2. Transitionの定義
コードまたはxmlで遷移する際に実行されるTransitionを定義します。

```java:
TransitionSet ts = new TransitionSet();
ts.addTransition(new Fade());
ts.addTransition(new Slide());
```

```xml:res/transition/base_transition.xml
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"  >
    <fade />
    <slide />
</transitionSet>
```

###3. FragmentにTransitionの設定
FragmentにTransitionを設定しますが、以下のパターンがあります。遷移元を<font color="red">FROM</font>,遷移先を<font color="blue">TO</font>とすると

#####ExitTransition
<font color="red">FROM</font>から<font color="blue">TO</font>を呼び出した時、<font color="red">FROM</font>のViewが消えるアニメーション
######EnterTransition
<font color="red">FROM</font>から<font color="blue">TO</font>を呼び出した時、<font color="blue">TO</font>のViewが現れるアニメーション
######ReturnTransition
<font color="blue">TO</font>から<font color="red">FROM</font>に戻る時、<font color="blue">TO</font>のViewが消えるアニメーション
デフォルトだとEnterTransitionと同じものが使われます
######ReenterTransition
<font color="blue">TO</font>から<font color="red">FROM</font>に戻る時、<font color="red">FROM</font>のViewが現れるアニメーション
デフォルトだとExitTransitionと同じものが使われます

以上のパターンについてFragmentにsetterが存在するので、2.で定義したTransitionを設定します。

```java:Fragment
Fragment frag = new NextFragment();
frag.setEnterTransition(ts);
```
xmlで記述も出来ます。

```style.xml
<resources>
    <style name="Theme.Base" parent="android:Theme.Material.Light">
　　　　　<item name="android:fragmentEnterTransition">
              @transition/base_transition
         </item>
    </style>
</resources>
```


###4. SharedElementの設定
Viewを共有するような形でシームレスに遷移させる場合はSharedElementを設定します。
その際にはSharedElement用にTransitionを設定し、遷移前後で共有するViewに同じTransitionNameをつけなければなりません。
SharedElementを利用しなければ以下の設定は必要ありません。

```java:遷移元Fragment
//Transitionを設定する
Frag frag = new TransitionFragment();
frag.setEnterTransition(set);
frag.setSharedElementEnterTransition(set);

//共有するViewにTransitionNameをつける
View target = findViewById(R.id.sharedElement);
target.setTransitionName("share");

//SharedElementとして設定する
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.container,frag).addSharedElement(target, transitionName);

```

```java:遷移先Fragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_next, container, false);
    View v = rootView.findViewById(R.id.sharedElement);
    v.setTransitionName("share");
       
    return rootView;
}
```
###5. Fragmentの起動
あとは起動するだけです。まとめてみると

```FragmentTransitionActivity.java
public class FragmentTransitionActivity extends Activity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction()
                    .add(R.id.container, new TransitionFragment())
                    .commit();
        }
    }

    @Override
    public void onClick(View v) {
        TransitionSet set = new TransitionSet();
        set.addTransition(new ChangeImageTransform());
        set.addTransition(new ChangeBounds());

        View target = v.findViewById(R.id.sharedElement);
        Fragment frag = new NextActivity.PlaceholderFragment();
        frag.setEnterTransition(set);
        frag.setSharedElementEnterTransition(set);
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.replace(R.id.container,frag )
                .addSharedElement(target, "share")
                .addToBackStack(null)
                .commit();
    }

    public static class TransitionFragment extends Fragment {
        @Override
        public View onCreateView (LayoutInflater inflater, ViewGroup container,
                                  Bundle savedInstanceState){
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            View target = rootView.findViewById(R.id.sharedElement);
            target.setTransitionName("share");
            target.setOnClickListener((ForthActivity)getActivity());
            return rootView;
        }

    }
}

```

<br>
##Activity Transitions
###1. window contentのTransitionの有効化
Fragmentと同様に設定します。
####2. Transitionの定義
Fragmentと同様に定義します。
####3. ActivityにTransitionの設定
Activityの場合はWindowに対してTransitionを設定します。

```java:
getWindow().setExitTransition(ts);  
getWindow().setEnterTransition(ts); 
```

####4. SharedElementの設定
Activityの場合は共有するViewに同じTransitionNameをつけ **なくても** いいみたいです。遷移先のViewにTransitionNameで設定しActivityOptions.makeSceneTransitionAnimation()で指定すればよさそうです。
SharedElementが１つの場合はそのままViewとTransitionNameを指定出来ますし、複数の場合はPair<View, String>を使えます。

SharedElementを利用しない場合は、第2引数をnullにしてActivityOptionsを生成します。

```java:遷移元Activity
//SharedElementを利用しない
ActivityOptions option0 = ActivityOptions.makeSceneTransitionAnimation(this, null);

//SharedElementが1つだけ
View target1 = findViewById(R.id.sharedElement1);
ActivityOptions options1 = 
    ActivityOptions.makeSceneTransitionAnimation(this, target1, "share");

//SharedElementが複数の場合
View target2 = findViewById(R.id.sharedElement2);
ActivityOptions options2 = ActivityOptions.makeSceneTransitionAnimation(this,  
                new Pair<View, String>(target1, "share1"),  
                new Pair<View, String>(target2, "share2")); 
```

```java:遷移先Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    View target1 = findViewById(R.id.sharedElement1);
    target1.setTransitionName("share1");
    View target2 = findViewById(R.id.sharedElement2);
    target2.setTransitionName("share2");
}
```
####5. Activityの起動
4.で生成したActivityOptionsを利用して起動します。
まとめるとこんな感じ。

```ActivityTransitionActivity.java
public class ActivityTransitionActivity extends Activity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        setContentView(R.layout.fragment_main);
        TransitionSet ts = new TransitionSet();
        ts.addTransition(new Fade());
        ts.addTransition(new Explode());
        getWindow().setExitTransition(ts);
        getWindow().setEnterTransition(ts);

        View v = findViewById(R.id.image);
        v.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, NextActivity.class);
        View target = v.findViewById(R.id.image);
        ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this, target, "share");
        startActivity(intent, options.toBundle());
    }
}                   
```

#まとめ
Lollipopからは、Activity/Fragmentの遷移アニメーションについてより柔軟に設定が可能になりました。
特にSharedElementに関しては実装は簡単に行えるのにUI上の効果は大きいです。([Android Transisions](https://www.youtube.com/watch?v=RhiPJByIMrM)の通り従来の方法で実装しようとしましたが、断念。。)
実はAndroid studio 1.0のSampleに
[ActivitySceneTransitionBasic](https://github.com/googlesamples/android-ActivitySceneTransitionBasic)があるのでこれをImportして動かしてみるのが一番早いです。
よくあるMaster-Detail構成で、そのActivity遷移にTransitionを使っています。
SampleにFragment Transitionというのもありますがこれは本記事の内容とは違いTransitionでなくAnimationを使うサンプルなのでお気をつけ下さい。