画面遷移を滑らかに (Activity Transitions)


## 今更ながらActivity Transitions
Android Developersを読みながら，Activity Transitionsを実装しました．
![ezgif.com-optimize.gif](https://qiita-image-store.s3.amazonaws.com/0/80986/eeff9e4d-7534-b5a1-40b7-18af6dcf1a3d.gif)
やったことを書きます．

## 手順
やったことは大きく分けて３つでした．

1. Activity Transitions用のテーマの作成
2. 2つのActivityで共有するViewを決める
3. startActivityをActivity Transitons用に書き換える

## ActivityTransitions用のテーマの作成
今のテーマはこうなってます．

```xml:res/values/styles.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/primary</item>
    <!-- 省略 -->
</style>
```

それをこうしておきます．

```xml:res/values/styles.xml
<style name="AppTheme" parent="AppTheme.Base" />

<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/primary</item>
    <!-- 省略 -->
</style>
```
AppThemeにあった内容を，新しく作ったAppTheme.Baseに移しました．

ActivityTransitionsに必要な属性は，minSdkVersionが21以上でないと使えません．
minSdkVersionは変えたくないので，`res/values-v21/styles.xml`で属性の追加を行います．

`res/values-v21/styles.xml`を作成し，以下の内容を書きます．

```xml:res/values-v21/styles.xml
   <style name="AppTheme" parent="AppTheme.Base">    <!-- AppTheme.Baseを継承 -->
        <item name="android:windowContentTransitions">true</item>
        <item name="android:windowAllowEnterTransitionOverlap">true</item>
        <item name="android:windowAllowReturnTransitionOverlap">true</item>
        <item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
        <item name="android:windowSharedElementExitTransition">@android:transition/move</item>
    </style>
```
AppTheme.Baseを作ったのは，ここで余計なコードを書かないためです．


そして，遷移を行う2つのアクティビティ`ImageListActivity`と`ImageDetailActivity`に，
このテーマを適用します．

```xml:AndroidManifest.xml
<activity
    android:name=".ui.ImageListActivity"
    android:theme="@style/AppTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name=".ui.ImageDetailActivity"
    android:theme="@style/AppTheme" />

```
これで，テーマの設定は完了です．

## 2つのActivityで共有するViewを決める
リストから詳細画面への遷移なので，クリックした画像(`ImageView`)を共有することにします．
変更するのは，共有するViewを定義している2つのレイアウトファイルです．

こちらも，minSdkVersionが21より小さいアプリでも対応できるよう，
res/layout-v21というディレクトリを作って，そちらに変更を行ったファイルを置いていきます．

具体的には，
`res/layout/row_list.xml`は，`res/layout-v21/row_list.xml`にコピーして，`res/layout-v21/row_list.xml`の方で変更を行います．

では，`res/layout-v21`にコピーした，リストのlayout定義ファイルのImageViewに変更を加えます．
ListViewのArrayAdapterの中で，このように使っているレイアウトファイルのことです．
`convertView = mInflater.inflate(R.layout.row_list, parent, false);`

```xml:res/layout-v21/row_list.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/row_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/row_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:transitionName="@string/trans_name" />    <!-- new line -->

    <!-- 省略 -->
```

`android:transitionName="@string/trans_name"`を共有したいImageViewの属性に追加しました．
`@string/trans_name`は下のように定義した，ただの文字列です．

```xml:res/values/strings.xml
<string name="trans_name">list_to_detail</string>
```

詳細画面のレイアウトファイルでも同じことをします．

```xml:res/layout-v21/activity_detail.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/detail_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/detail_image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:transitionName="@string/trans_name" />    <!-- new line -->

    <!-- 省略 -->
```
`android:transitionName="@string/trans_name"`を共有したいImageViewの属性に追加しました．

この`android:transitionName`によって，
リストの画像は詳細画面のどこに移動すれば良いのか分かるようになります．

あとは，startActivityの部分を書き換えれば完成です．

## startActivityをActivityTransitions用に書き換える
リストのクリックに合わせて詳細画面に行く部分はこんな感じでした．

```java:ImageListActivity.java
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        ListItem item = mAdapter.getItem(position);
        Intent intent = new Intent(mActivity, ImageDetailActivity.class);
        intent.putExtra(ImageDetailActivity.EXTRA_CONTENT, item.toJson());
        startActivity(intent);
    }
});
```

それがこうなります．

```java:ImageListActivity.java
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
             mActivity,
             view.findViewById(R.id.row_image),
             getString(R.string.trans_name) );
         ListItem item = mAdapter.getItem(position);
         Intent intent = new Intent(mActivity, ImageDetailActivity.class);
         intent.putExtra(ImageDetailActivity.EXTRA_CONTENT, item.toJson());
         ActivityCompat.startActivity(activity, intent, options.toBundle());
    }
});
```

これで完成です．

##最後に
GitHubにあった，[MaterialEverywhere](https://github.com/antoniolg/MaterialEverywhere)と[MaterialDesignSample](https://github.com/rejasupotaro/MaterialDesignSample)が，とても参考になりました．

## 参考URL
- [Defining Custom Animations | Android Developers](http://developer.android.com/training/material/animations.html)
- [MaterialEverywhere](https://github.com/antoniolg/MaterialEverywhere)
- [MaterialDesignSample](https://github.com/rejasupotaro/MaterialDesignSample)