SharedElementTransition with Navigation Architecture Component

## この記事は？

- RecyclerView（一覧画面） -> 詳細画面　の構成
- SharedElementTransition　の適用
- Fragment -> Fragment 遷移をNavigation Architecture Componentで行う

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53158/b6703b2c-a5e0-bfbc-542d-828b7bb34295.gif)



## 利用SDK

```gradle:project/build.gradle
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0-alpha02"
```

```gradle:app/build.gradle
apply plugin: 'androidx.navigation.safeargs.kotlin'

// 省略

implementation "androidx.navigation:navigation-fragment-ktx:2.1.0-alpha02"
```

## 手順まとめ

### 一覧画面

- `postponeEnterTransition()`を実行しておく
- RecyclerViewの描画直前に、`startPostponedEnterTransition()`を実行
- 共有したいViewに一意の`TransitionName`を設定
  - RecyclerView内のアイテムなら、position等を利用して一意にする
- 詳細画面に`TransitionName`を渡す
- 遷移処理時に、`addSharedElement`をする

### 詳細画面

- `sharedElementEnterTransition`を設定する
- 前画面と共有するViewに渡されてきた`TransitionName`を設定する

## 実装

### Navigation Graph

xmlを記載したら、一度ビルドしましょう。
後ほど必要なクラスが生成されます。

```xml:nav_graph
<?xml version="1.0" encoding="utf-8"?>
<navigation
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home_fragment"
    >

    <!-- 省略 -->
 
   <!-- 一覧画面 -->
    <fragment
        android:id="@+id/todo_list_fragment"
        android:name="com.helmos.mysandbox.presentation.todo.TodoListFragment"
        android:label="TodoListFragment"
        tools:layout="@layout/fragment_todo_list"
        >
        <action
            android:id="@+id/start_detail"
            app:destination="@id/todo_detail_fragment"
            >
            <argument
                android:name="todo"
                app:argType="com.helmos.mysandbox.data.entity.Todo"
                />
            <argument
                android:name="transition_name"
                app:argType="string"
                />
        </action>
    </fragment>

    <!-- 詳細画面 -->
    <fragment
        android:id="@+id/todo_detail_fragment"
        android:name="com.helmos.mysandbox.presentation.todo.detail.TodoDetailFragment"
        android:label="TodoDetailFragment"
        tools:layout="@layout/fragment_todo_detail"
        >
        <argument
            android:name="todo"
            app:argType="com.helmos.mysandbox.data.entity.Todo"
            />
        <argument
            android:name="transition_name"
            app:argType="string"
            />
    </fragment>
</navigation>
```

### 一覧画面

RecyclerViewの使い方などは省略しています。

```kotlin:TodoListFragment
override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {

    // 省略

    // RecyclerViewのTransitionは遅延させる
    postponeEnterTransition()
    todoRecycler.viewTreeObserver.addOnPreDrawListener {
        startPostponedEnterTransition()
        true
    }
}

// onBindViewHolder　共有要素にTransitionNameを設定
view.transitionName = "todo_transition_$position"

// 一覧アイテムクリック時の処理
setOnItemClickListener { item, view ->
    val todo = (item as TodoItem).todo
    val sharedView = view.findViewById<View>(R.id.todo_icon)
    // 画面遷移
    findNavController().navigate(
            // 自動生成されるDirectionsクラス
            TodoListFragmentDirections.startDetail(todo, sharedView.transitionName),
            // addSharedElementを行ってくれるUtilクラス
            FragmentNavigatorExtras(
                    sharedView to sharedView.transitionName
            )
    )
}
```

### 詳細画面

```kotlin:TodoDetailFragment

// 自動生成される引数データクラス
private val args by navArgs<TodoDetailFragmentArgs>()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
}

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {

    // 省略

    // 共有要素に設定
    todoImage.transitionName = args.transitionName

}
```

## 迷った点/今後調べること

**`android.R.transition.move`を使わないとダメだった**

自作のtransitionだと、
Activity->Activity時には動いていたが、Fragment->Fragmentでは動作しなかった
何か制約があるのかも？

移動させるだけであれば、`android.R.transition.move`でOK

**詳細から戻った時にアニメーションしなかった**

RecyclerView描画直前に`startPostponedEnterTransition()`を実行して解決
こちらもActivity->Activity時には設定していなくても動いていたので、Fragment間の遷移だと色々と勝手が違いそう？？