[Android] Navigation Architecture Component で、Shared Element Transition を使用した遷移方法

Navigation Architecture Component で、Shared Element Transition を使用した遷移方法

Android Architecture Components の Navigation Architecture Component で、Shared Element Transition を使用した遷移方法が、よくわからなんかったので調査してみました。

参考にしたコード

とりあえず、下記の2パターンで遷移できるとこまで作成

  • 通常のViewをクリックしただけの遷移
  • RecyclerViewで表示されている要素からの遷移

Navigation Component と Shared Elementsを使用した遷移

通常のNavigationComponentでの遷移時に、追加でFragmentNavigatorExtrasを使用してSharedElementsを指定します。

        view.findViewById<View>(R.id.imageView).setOnClickListener {
            it.findNavController().navigate(
                TransitionSourceFragmentDirections.actionTransitionSourceFragmentToTransitionDestinationFragment(),
                FragmentNavigatorExtras(
                    it to "testTransitionName"
                )
            )
        }

遷移先では、通常のSharedElementsを使用した遷移と同様に,Viewに同じtransitionNameを指定しておき、
遷移時のアニメーションを指定します。

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(R.transition.move)
        return inflater.inflate(R.layout.fragment_transition_destination, container, false)
    }

これで、NavigationComponentを使用しながら、ShareadElementsも使用して遷移ができるようになります。
バックボタンで、前の画面に戻る際にも、アニメーションが入るようになります。

RecyclerViewを使用している場合の実装

RecyclerViewで選択した要素から、遷移する場合は上記だけでは、うまく動作しません。

追加で必要なポイントとしては、
* RecyclerViewの準備ができてから、遷移アニメーションを行うようにする
* 要素ごとにユニークなtransitionNameを使用する
になります。

まず、RecyclerViewの準備ができてから〜、の部分はviewTreeObserverを使用します。
いったんpostponeEnterTransitionでアニメーションを止めて、RecyclerViewの描画が終了してからアニメーションを開始させます。

これによって、バックボタンでRecyclerViewに戻った際にも、アニメーションが行われるようになります。

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        postponeEnterTransition()
        view.viewTreeObserver
            .addOnPreDrawListener {
                startPostponedEnterTransition()
                true
            }
    }

次に、要素ごとにユニークなtransitionNameを使用するの部分は、
RecyclerView.Adapterで、各要素のViewをbindする際に、ユニークなtransitionNameをbindします。

    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = mValues[position]
        val transitionView = holder.itemView.findViewById<View>(R.id.imageView)
        transitionView.transitionName = item.id

    }

遷移時には、遷移先のViewにtransitionNameを渡すようにします。
これで、遷移先でも同じtransitionNameを使用できます。

    override fun onClickItem(transitionView: View) {
        view.findNavController().navigate(
            TransitionSourceGridFragmentDirections.actionTransitionSourceGridFragmentToTransitionDestinationGridFragment(
                transitionView.transitionName
            ),
            FragmentNavigatorExtras(
                transitionView to transitionView.transitionName
            )
        )
    }

遷移先では、受け取ったtransitionNameを自身のViewにセットします。

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        arguments?.let {
            view.findViewById<View>(R.id.imageView).transitionName =
                TransitionDestinationGridFragmentArgs.fromBundle(it).transitionName
        }
    }

まとめ

全体のコードは、ココにおいてあります。

やり方さえ分かれば、大した実装ではないです。
が、なかなかやり方がわからず、サンプルコードを見つけるまで苦労しました。