Shared element transitionでリストの要素を並び替える
やりたいこと
Fragmentを切り替えてReordering(要素を並び替え)する shared element transition
の記事を見て楽しそうだったからやってみた。
medium.com
全く同じことをやっても楽しくないので、Fragment <-> Activityでやってみる。
できた
いきなりだけど成果物
コードはこの辺
やったこと
Activityで shared element transition
するには startActivity()
するときに第2引数にtransition animationのbundleを渡してやる必要がある。
transition animationを作るのに必要なのは下記のPair.
- 移動元になるview
- 上記viewに対して一意になるID
今回はリストの並び替えなので上記のpairを並び替えたいViewの数だけ持つ必要がある。
RecyclerView
でリストを表示している場合、Adapterの onBindViewHolder
で各Viewにアクセスできるのでこれを保持しておく。
class ReorderingAdapter(private val activity: Activity) : RecyclerView.Adapter<ReorderingViewHolder>() { val items = mutableListOf<ImageView>() // adapterのpropertyとしてmutableListを持つ override fun onBindViewHolder(holder: ReorderingViewHolder, position: Int) { holder.bind(position) if (!items.contains(holder.image)) { items.add(holder.image) // 画面に表示されたタイミングでlistに追加 } } }
実際に並び替えを実行するタイミングで保持していたviewのリストからtransition animation用のbundleを生成する。
fab.setOnClickListener { context?.let { val itemList = mutableListOf<Pair<View, String>>() (recycler.adapter as? ReorderingAdapter)?.items?.forEachIndexed { index, view -> itemList.add(Pair(view, IMAGE_TRANSITION_NAME + index)) } val options = ActivityOptionsCompat.makeSceneTransitionAnimation( activity as Activity, *itemList.toTypedArray() ).toBundle() ReorderingActivity.start(it, options) } }
並び替え後のviewの方でも同じIDを設定する。
itemView.setTag(R.id.position, position) ViewCompat.setTransitionName(image, IMAGE_TRANSITION_NAME + position)
あとは普通の Shared element transition
のように postponeEnterTransition()
したり見た目の微調整して終わり。
注意点として、画面に表示されていないものはanimationの対象にならない。
そもそも onBindViewHolder()
を通らないとitemsのlistにaddできないが、仮にlistに存在していたとしても画面外だったらanimationされない。RecyclerViewを使っていたのですでにrecycleされてしまっている場合はviewがなくなっちゃってるからanimationできないのかな。
おまけ
transition animation用のbundleを生成する時に ActivityOptionsCompat#makeSceneTransitionAnimation() を使うが、このメソッドの第2引数が可変長引数になっている。
@NonNull @SuppressWarnings("unchecked") public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity, Pair<View, String>... sharedElements) { if (Build.VERSION.SDK_INT >= 21) { android.util.Pair<View, String>[] pairs = null; if (sharedElements != null) { pairs = new android.util.Pair[sharedElements.length]; for (int i = 0; i < sharedElements.length; i++) { pairs[i] = android.util.Pair.create( sharedElements[i].first, sharedElements[i].second); } } return createImpl(ActivityOptions.makeSceneTransitionAnimation(activity, pairs)); } return new ActivityOptionsCompat(); }
可変長引数にListを渡すときは一度Arrayに型変換してからspread operator(*
)を使う。
*itemList.toTypedArray()