AndroidではAcitivtyやFragmentの遷移時に両方の画面に共通する要素を指定することで、それらがアニメーションで連続的に繋がっているように見えるShared Element Transitionという機能があります。
最初の画面左上にあるアイコンと、その後起動される画面中央にあるアイコンをつなげて以下のようなアニメーションになります。
共通要素のtransitionNameに同じ名前を設定して、起動時にオプションを設定するというシンプルな実装でこの高度なアニメーションを実行してくれます。
さて、これをカスタムViewで実装するとなぜかうまくアニメーションしてくれませんでした。
実装は簡単だけど、アニメーションがどのように実現されているのかについて深く考えたことはありませんで。なんとなく、起動前と起動後のViewのスナップショットをとってそのスナップショット画像を描画してくれてるんだろう、みたいに認識してましたが、全然違いましたというお話。
調査:カスタムViewにログを仕込む
泥臭くログ出力を仕込んで調べて見ましょう
class MyImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
): AppCompatImageView(context, attrs, defStyleAttr) {
private val index = count++
override fun layout(l: Int, t: Int, r: Int, b: Int) {
super.layout(l, t, r, b)
Log.e("XXXX", "$index layout: $width $height")
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
Log.e("XXXX", "$index draw $width $height")
}
companion object {
private var count: Int = 0
}
}
ImageViewを拡張したカスタムViewで上記のようにログを仕込んで実行してみます
startActivityでは以下のようなログになります
0 draw 189 189
1 layout: 525 525
1 layout: 189 189
1 layout: 525 525
1 layout: 525 525
1 draw 189 189
1 draw 189 189
1 draw 192 192
1 draw 199 200
1 draw 211 212
1 draw 229 228
1 draw 250 250
1 draw 273 273
1 draw 300 301
1 draw 329 329
1 draw 357 357
1 draw 386 387
1 draw 416 416
1 draw 441 441
1 draw 465 465
1 draw 487 487
1 draw 503 502
1 draw 515 515
1 draw 522 522
1 draw 525 525
finishAfterTransitionでは以下のようなログになります
1 draw 525 525
0 draw 189 189
1 layout: 189 189
1 draw 525 525
1 draw 525 525
1 draw 522 522
1 draw 515 515
1 draw 503 502
1 draw 485 486
1 draw 465 465
1 draw 442 441
1 draw 416 416
1 draw 386 387
1 draw 357 357
1 draw 329 329
1 draw 300 301
1 draw 273 273
1 draw 250 250
1 draw 229 228
1 draw 211 212
1 draw 199 200
1 draw 192 192
1 draw 189 189
0 layout: 189 189
0 layout: 189 189
0 layout: 189 189
0 draw 189 189
要するに起動されるActivity側のViewのdrawによって描画されているってことですね。しかもlayoutが呼ばれず、drawの度にサイズが変化しています。カスタムViewでうまくアニメーションされなかったのは、このような描画のされ方を想定して実装していなかったからでした。
結論:起動されるActivityのViewが描画していた
ということで、起動されるActivity上のSharedElementとなるViewは、measureやlayoutの呼び出しなしにdrawが呼び出されるため、このときに適切な描画処理を行わないとSharedElementTransitionがうまく動作しないということになります。