Edited at
GoodpatchDay 18

Androidで丸いCardViewを滑らかにページ遷移させる

この記事は Goodpatch Advent Calendar 2018 18日目の記事です。

Google I/O 2018でMaterial Themingが発表され、最近のGoogle製アプリではGoogle独自のMaterial Designを採用したアプリが増えてきましたね。

また、その影響やiOS側のトレンドの変化もあって最近では、様々なアイコンやViewの形状が丸くなってきてたように思います。

そのあたりの考察は別の記事に譲るとして、今回はAndroid開発者として、角が丸めのCardViewを実装した時に困ったことと、その対処法をご紹介します。


cardCornerRadius=16dpのCardView

まずは最近流行りの丸いCardViewを作ります。

分かりやすいようにcardCornerRadiusは16dpにしておきましょう。

<androidx.cardview.widget.CardView

android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="16dp"
app:cardElevation="1dp">

はい、できました。簡単ですね。


Transition Animationがうまくいかない

このCardViewにShared Elementsを使ったTransition Animationを付けて別のActivityに遷移させてみます。

animation10.gif

分かりやすいようにWindow Animation Scaleはx10です。

アニメーションが始まった瞬間にCardViewでClipされていた四角い画像が表に出てきているのが分かりますか?

Shared Elementを使ったときに遷移中に表示されるViewは遷移後のActivityのViewです。このためClipされていない遷移後のViewがタップ直後に表示されてしまいます。

これではせっかくのCardViewが少し残念です。


Transition中のViewのサイズに合わせてClipするradiusを変化させる

今回はCardViewの中に使うViewをpathでclipすることで滑らかにアニメーションさせるようにしてみました。

Transition Animation時にViewのサイズが変わるたびにonDrawが呼びだされるため、その時のViewの幅に合わせて少しずつRadiusが変化するようにします。

private val rootWidth = resources.displayMetrics.widthPixels

private val cornerRadius: Float = resources.getDimension(R.dimen.card_corner_radius)
private val cardSideMargin: Float = resources.getDimension(R.dimen.card_side_margin) * 2

override fun onDraw(canvas: Canvas) {
// viewの幅に合わせてcornerRadiusを変化させる
val radius = Math.min(cornerRadius, cornerRadius * ((rootWidth - this.width) / cardSideMargin))
clipPath.reset()
clipRectF.set(canvas.clipBounds)
clipPath.addRoundRect(clipRectF, radius, radius, Path.Direction.CW)
canvas.clipPath(clipPath)
super.onDraw(canvas)
}

animation11.gif

いい感じになりました。(個人の感想です。

今回のすべてのサンプルはこちらです。

ymegane/CardAnimationSample | GitHub

これで週末は心置きなく映画館に行けそうです。

みなさんよいクリスマスを。