アウトプットのネタが無くて困っている人へ~Jetpack Composeでクリスマスイルミネーションボタンの作り方添え~

こんにちは！
ZOZOTOWNのAndroidアプリ開発していますとは言えなくなってきたumsysです。
去年辺りからロールがマネジメント職に変わりまして、予算の話とか仕事のスケジュール調整とかばっかりやってます。
なので、記事のメインを張れるような大きいネタが無いため、今年はポエムでいかせていただきます。

# ズバリ「アウトプットのネタが無い」を解決する方法
ネタが無いと言ったばかりの私が言うのもなんですが、「アウトプットのネタが無くて困っている」という話をよく聞きます。
僕自信も昔はこのことで困っていましたが、あることを意識するだけで困ることがなくなりました。
そのあることとは…

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323027/732e8bd3-5a68-08c4-5436-0ff1f3591783.png">

子供の頃に持っていた「パパ〜！ママ〜！みんな〜！見て見て〜！」という単純な承認欲求を意識することです。
みなさんも子供の頃、一度は口にしたことないでしょうか？
「ちょっといいものできた！見てほしい！」
「すごい発見をした！見てほしい！」
この気持ちを、今の自分に当てはめてみてください。
もし、この気持ちを発散できないのであれば、おそらく手を動かす量が足りていないかもしれません。
私自身も最近は開発から遠のいてるので、開発に関する「見て見て〜！」が無く、今回このようなポエムに至ったわけです。

手を動かす量が十分な場合、記憶の底に埋もれている可能性もあります。
過去のプルリクなどをじっくり振り返ってみてはいかがでしょうか？

# 既に記事が世の中に出ていて萎縮してしまう
「見て見て〜！」という気持ちが出てきても、もう一段階壁があります。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323027/40c556ac-8872-f404-16a6-a055a0ff7eff.png">

自分が書こうとしたアウトプットと同じような内容が、既に世の中に出ていて萎縮してしまうことです。
その萎縮は謙虚な姿勢の現れで素晴らしいことだと思いますが、同じような内容でも必ず小さい差はあるはずです。
その小さい差ですが、例えば使っている機材やツールの環境差分です。
パソコンの型番やOSの種類、OSバージョン、メモリ数、環境変数などの細かい情報まで書くことで、唯一無二の記事になるはずです。
実際、困ったことに直面したとき、記事を発見して実践してもうまくいかないことがありませんでしたか？
私は、OSバージョンの違いやターミナルのデフォルトシェルの違いなどが原因で躓いたことがありますOTL

他にも、同じAPIの解説でも作っているものの方向性が違ったり、用途の具体例を書いたり、様々な設定値による検証結果を書いてみたり。
その小さい差をオリジナリティとして心の支えにし、勇気を持って自分も記事をアウトプットしてみましょう！

# おまけ
ここまで色々書かせていただきましたが、書いているうちに「見て見て〜！」が湧いてきたので、小さい技術tipsを添えて終わりにしようと思います。

## infiniteTransitionと三角関数を使ってComposeだけのアニメーションを作る
Android開発においてアニメーションの実装をしようとする場合、だいたい`Lottie`が使われるのではないでしょうか？
でも簡単なアニメーションであれば、　Composeだけでもなんとかなっちゃいます。
もちろん、あまりに複雑なものだと返って動作が重くなるので、ケースバイケースですが…

今回は、`rememberInfiniteTransition`と三角関数を使って、クリスマスイルミネーションの如く派手に、色と形が変化するボタンを作ります。

### まず完成品から
こんな感じボタンができます。
~~クリスマスイルミネーションというより、怪しい広告の購入ボタンみたいになっちゃった~~

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323027/5b106a7d-722b-514b-42b3-11e2e736eaa1.gif">

コードはこちら
```kotlin
package com.example.sankakukansu

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlin.math.cos
import kotlin.math.sin

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Composable
fun MyApp() {
    MaterialTheme {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = androidx.compose.ui.Alignment.Center
        ) {
            GlowingButton()
        }
    }
}

@Composable
fun GlowingButton() {
    // 無限アニメーションを作成
    val infiniteTransition = rememberInfiniteTransition(label = "")

    // 時間に応じて値を変化させるアニメーション
    val time by infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 1f,
        animationSpec = infiniteRepeatable(
            animation = tween(
                durationMillis = 500,
            ),
            repeatMode = RepeatMode.Reverse
        ), label = ""
    )

    // 三角関数を使用して動的な値を生成
    val animatedHeight = 80f + 60f * sin(time)
    val animatedWidth = 200f + 80f * sin(time)
    val animatedAlpha = 0.5f + 0.3f * cos(time)

    // 色相 (Hue) を三角関数で変化
    val animatedHue = (180f + 180f * sin(time)) % 360f
    val glowColor = Color.hsv(animatedHue, 1f, 1f)

    Box {

        // ボタン本体
        Button(
            onClick = { /* アクション */ },
            shape = RoundedCornerShape(20.dp),
            colors = ButtonDefaults.buttonColors(containerColor = glowColor.copy(alpha = animatedAlpha)),
            modifier = Modifier
                .height(animatedHeight.dp)
                .width(animatedWidth.dp)
        ) {
            Text("めりーくりすます", style = TextStyle(color = Color.White, fontSize = 18.sp))
        }
    }
}
```
以下、解説です。

### rememberInfiniteTransition
```kotlin    
    // 無限アニメーションを作成
    val infiniteTransition = rememberInfiniteTransition(label = "")

    // 時間に応じて値を変化させるアニメーション
    val time by infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 1f,
        animationSpec = infiniteRepeatable(
            animation = tween(
                durationMillis = 500,
            ),
            repeatMode = RepeatMode.Reverse
        ), label = ""
    )
```
`rememberInfiniteTransition`は、Jetpack Composeで「永続的にループするアニメーション」を作成するためのAPIです。
`animateFloat`でアニメーションの開始値(`initialValue`)と終了値(`targetValue`)を指定し、それを`durationMillis`で指定した間隔でループさせます。

`repeatMode`は繰り返し方法です。`Reverse`では値が終了値に達すると逆方向に戻ります。色の遷移をクラデーションで繋ぎたかったので、行ったり来たりを繰り返す`Reverse`を使いました。

### 三角関数
色と形の変化に緩急をつけるため、三角関数を使いました。
みなさんは学生の頃に勉強した三角関数のグラフを覚えていますか？

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323027/633c9dcd-58cb-5240-8ed9-558fde8336b0.png">

0と1の近辺で数値の変化量が小さくなる特性を持ったsinとcosは、緩急表現を作るのにうってつけです。
```kotlin
    // 三角関数を使用して動的な値を生成
    val animatedHeight = 80f + 60f * sin(time)
    val animatedWidth = 200f + 80f * sin(time)
    val animatedAlpha = 0.5f + 0.3f * cos(time)

    // 色相 (Hue) を三角関数で変化
    val animatedHue = (180f + 180f * sin(time)) % 360f
    val glowColor = Color.hsv(animatedHue, 1f, 1f)
```
高さや色相にそれぞれの三角関数を入れてあげることで、緩急のついた数値が永続的に流れ続けます。
足し算で数値を分けていますが、これは最小値を保証するためです。分けずにすべてを三角関数にかけてしまうと0になってしまうため、消えては現れるボタンになってしまいます。（それはそれで面白いかも。）

### 作った数値をComposeに合体
```kotlin
        // ボタン本体
        Button(
            onClick = { /* アクション */ },
            shape = RoundedCornerShape(20.dp),
            colors = ButtonDefaults.buttonColors(containerColor = glowColor.copy(alpha = animatedAlpha)),
            modifier = Modifier
                .height(animatedHeight.dp)
                .width(animatedWidth.dp)
        ) {
            Text("めりーくりすます", style = TextStyle(color = Color.White, fontSize = 18.sp))
        }
```
サブタイトル通りです。
作った値は`Float`なので、そのまま各設定値にいれることができます。
これで完成！

# 注意書き
※この記事は、ChatGPTによって生成された画像やコードの一部をつかって作成されています。ご了承ください。