【Android】ニコニコのコメント風アニメーションを実装する【Animation】


はじめに

デザイナー「ここボタン押したくなるようなアニメーションさせてください!」

ぼく「わかりました!」

ってことで、ニコニコのコメント風アニメーションを実装することにした。
思ったより簡単にできたので、実装方法をご紹介。

完成形

こんな感じのものを作るよ。
前提として、ルートのViewGroupはConstraintLayoutを使ってる。

やること

  1. 画面サイズ取得
  2. TextViewを生成する
  3. Y位置をランダムで設定する
  4. 生成したTextViewに制約を設定する
  5. 右から左に移動する

1.画面サイズ取得

3の「Y位置をランダムで設定」 と5の「右から左に移動」 をするのに、画面サイズが必要になる。
幅と高さ別々で取得してもいいんだけど、画面サイズのクラス作っちゃった方が僕は好み。

data class WindowSize(
    val width: Int,
    val height: Int
)

private fun getWindowSize(): WindowSize {
    val outMetrics = DisplayMetrics()
    display?.getRealMetrics(outMetrics)
    return WindowSize(
        width = outMetrics.widthPixels,
        height = outMetrics.heightPixels
    )
}

2.TextViewを生成

まずはコメントを表示させる画面に対してTextViewをインフレートさせる。

val textView = TextView(baseContext).apply {
    id = View.generateViewId()
    text = "Hello World!"
    layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
}
textView.setTextColor(Color.BLACK)
constraintLayout.addView(textView)

idはあとで使うから設定してるよ。

3.Y位置をランダムで設定

コメントが表示される高さの範囲は画面の高さ - TextViewの高さ で求められる。

これより下げちゃうと見切れちゃうからね。

val random = Random(System.currentTimeMillis())
val transitionY = random.nextInt(windowSize.height - textView.layoutParams.height)
textView.translationY = transitionY.toFloat()

乱数のシードはなんでもいいけど、今回はSystem.currentTimeMillis()を使うよ。

4.生成したTextViewに制約を設定する

設定する位置は画面の右側。

ConstraintSet().also {
    it.clone(constraintLayout)
    it.connect(
        textView.id,
        ConstraintSet.LEFT,
        ConstraintSet.PARENT_ID,
        ConstraintSet.RIGHT
    )
    it.applyTo(constraintLayout)
}

多分「何やってんのコレ?」って感じだろうけど、ConstraintSetについて調べればわかると思う。
本筋と外れるからここでは解説しないけど、@soranakk さんのKotlin(Java)コード上でConstraintLayoutの制約を編集するって記事がわかりやすく解説してくれてるよ。
あとは公式ドキュメント読んだら完璧。

5.右から左に移動する

これは多分色々やり方あるんだろうけど、今回はObjectAnimatorを使うよ。

移動する範囲は 画面幅 + TextViewの幅で求められるので、doOnLayoutを使ってレイアウトが決定したタイミングでアニメーションの設定をしてる。
これもうちょいいい感じにできそうだけどね。

textView.doOnLayout {
    val textViewWidth = it.width.toFloat()
    val objectAnimator = ObjectAnimator.ofFloat(
        textView,
        "translationX",
        0f,
        -(windowSize.width + textViewWidth)
    ).apply {
        interpolator = LinearInterpolator()
        duration = 3000L
    }.also {
        it.doOnEnd {
            // アニメーションが終わったViewを削除
            constraintLayout.removeView(textView)
        }
    }
    objectAnimator.start()
}

interpolatorはデフォルトのが気に入らなかったから、等速で移動するLinearInterpolatorに変更。

アニメーションが終わったらdoOnEndでremoveするのを忘れずに。
やらないとボタン押せば押すほどViewが溜まって、無限にアプリが重くなっていくよ。

ここまでやったらあとはボタンにクリックイベントつけて完成〜。

最後に

こういうの、意外と頑張ればなんとかなるものね。
また凝ったアニメーション挑戦したいなと思った。

一応ここでサンプル公開してるから、「わかんねえよ!!」ってなったら見てね。

おわり!