Android
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

これで週末は心置きなく映画館に行けそうです。
みなさんよいクリスマスを。