【Kotlin】Jetpack Composeでアプリを可愛くしてみた

この記事は [レコチョク Advent Calendar 2023](https://qiita.com/advent-calendar/2023/recochoku) の22日目の記事となります。 

## はじめに
こんにちは。Androidアプリ開発グループの塚本です。
日々、豆柴の大群というアイドルグループの追っかけをしていて、毎週のように現場に足を運んでいます。
以前のライブでは『僕がいい』『トラスト』『ガーデニング』という曲を聴いて、感動で号泣しました。
また今月はそれに加えて
- スマホゲーム『バンドリ！ガールズバンドパーティー！』ホロライブコラボ
- スマホゲーム、『プロジェクトセカイ カラフルステージ！ feat. 初音ミク』サンリオコラボ

で忙しい時を過ごしておりました。
ガルパメンバーとホロライブメンバーが一緒に歌ってるコラボ曲もとても良いです。

本記事では、そんな私がプロジェクトセカイをプレイしていて思いついたアイデアを、Androidアプリとして実現した経過をお伝えします。

## 記事を読んでできること
序論がやや長くなるので、まずはこの記事を読んで得られる具体的なスキルをまとめておきます。

- アプリ内のフォントを変更する方法
- 文字にマーカーペン風アンダーラインを引く方法
- アイコンボタンの実装方法
- 背景色をグラデーションにする方法
- Jetpack Composeで画像がバウンドするアニメーションを表示する方法

## 今回作成したアプリについて
今回私が作成したアプリのインスピレーション源は、プロジェクトセカイのキャラクター、[天馬咲希](https://pjsekai.sega.jp/character/unite01/saki/index.html)ちゃんが「高校卒業するまでにやりたい100のこと」のリストを作り、友達と一緒にそれを達成していくというストーリーでした。

このストーリーが私にとって感動的だった理由は、「やりたいこと100」は一生を通じての目標設定という印象を持っていたのに対し、それを短期間の小さな目標達成の手段として使っている点にありました。

私自身、手帳やノートを常に持ち歩く習慣がないので、自分のアプリを作成するという発想に至りました。しかし、ただ作るだけでは一般的なTODOリストアプリになりかねないため、今回は特定のテーマを設定しました。「咲希ちゃんが書いたように可愛らしいものを作ること」です。

今回は基本的なアプリの実装は割愛し、どのようにして咲希ちゃんが書いたような可愛らしさを追求したのかという見た目中心の実装を紹介していきます。

## 可愛いアプリとは
まずは私が作成した「やりたい100のこと」アプリをご覧ください。このアプリはKotlinとJetpack Composeを使用して作成しました。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/922d5f4e-be79-21ed-e2f4-db9601508a79.png">

TopBarと入力ボタンは可愛いピンク色にしておきました。
このアプリを可愛くするために、どういう工夫をしていくか、考えていきたいと思います。

ゲームのストーリー内では咲希ちゃんが作成したノートのデザインは具体的には描かれていません。そのため、まずは咲希ちゃんのキャラクターを分析し、その上でどのようなデザインにするかを考えました。その結果、以下の要素を持つデザインを作成することにしました。
- 高校生の女の子が書くような可愛い文字
- 目標を達成したくなるような、可愛いアクションを追加する
- 目標を達成した際に嬉しくなるような、アニメーションを追加する

## フォントの変更
### フォントの探し方
フォントは[Google Font](https://fonts.google.com/)で探しました。
その中でも、可愛らしい手書き風フォント[Yomogi](https://fonts.google.com/specimen/Yomogi)というフォントを見つけました。
ページ内にダウンロードボタンがあるので、フォントをダウンロードします。

### フォントの設定
1. res直下に「font」というフォルダを作成します。
2. ダウンロードしてきたフォント（ttfファイル）を「font」フォルダに追加します。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/1824c103-5a45-079f-c770-7d28f6f6499e.png">

3. 変更したいテキストの要素の fontFamily 属性にフォントを指定します。

```Kotlin
val yomogi = FontFamily(Font(resId = R.font.yomogi))
val typography = Typography(
     body1 = TextStyle(
         fontFamily = yomogi,
         fontSize = 20.sp
    )
)

Text(
    text = text,
    style = typography.body1,
 )
```

アプリの標準フォントに設定する際は、styleファイルの`android:fontFamily`で指定します。

```Kotlin
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme"">
        <!-- Customize your theme here. -->
        <item name="android:fontFamily">@font/yomogi</item>
    </style>
</resources>
```

結果がこちら

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/6d790e6c-efde-d89d-ca37-e0c221aae3a3.png">

女子高生の手書きみたいで超可愛い…。

## フォントにマーカーペンでアンダーラインを引く
咲希ちゃんは目標を書いたら、カラフルなペンで線を引いて強調するという~~妄想~~ 想像をしたので、
今回は、黄色い蛍光マーカーでアンダーラインを引いているような感じにしたいと思います。

文字全体に線を引くのではなく、文字の下半分に線が引かれるようにすることで、より咲希ちゃんが引くアンダーラインに近くなるのではと思ったので、そのようにしています。

```Kotlin
Text(
    text = task.name,
    style = typography.body1,
    modifier = Modifier
        .padding(horizontal = 8.dp)
        .weight(1f)
        .drawBehind {
            val strokeWidth = size.height / 2
            drawRect(
                color = Color.Yellow,
                topLeft = Offset(0f, size.height - strokeWidth),
                size = Size(size.width, strokeWidth)
            )
        }
)
```

結果がこちら

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/46a58810-f8b6-e405-e5f6-074c3035ae2a.png">

ちゃんとマーカーペンっぽくなってていいのですが、文字の長さだけ線を引きたかったですね、、、
端から端まで引かれてしまっているのが惜しいですね。

別の方法も試してみたいと思います。
```Kotlin
val text = buildAnnotatedString {
    withStyle(style = SpanStyle(background = Color.Yellow)) {
        append(task.name)
    }
}

Text(
    text = text,
    style = typography.body1,
    modifier = Modifier
        .padding(horizontal = 8.dp)
        .weight(1f)
)
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/8dddf860-7b9e-f417-4571-e5272afa273c.png">

文字の長さだけ、線を引くことに成功しましたが、「文字の下半分に線が引かれるようにする」というものと共存できなくなってしまいました。
今回使った`withStyle`では「文字の下半分に線が引かれるようにする」ための引数が無く、今後も研究が必要になってきますね。
今回は文字の長さだけ線を引くことを優先するため、後者を採用したいと思います。

フォント周りをカスタマイズする際は、一般的にアプリサイズが大きくなるため、お気をつけください。

次は、チェックボックスが物足りないので、これを改良していきます。

## チェックボックスをアイコンボタンに変更
咲希ちゃんは目標を達成したとき、どのようなことをするだろうと~~妄想~~ 想像した結果、
「元気に赤ペンで丸を書く」という決断に至りました。なんなら花丸を書くと思います。
手書きっぽいチェックボックスの画像と、花丸の画像を使って、アイコンボタンを作成します。
と、いうことで早速試しました。

```Kotlin
Box {
    Image(
        painter = painterResource(id = R.drawable.shikaku),
        contentDescription = null,
        modifier = Modifier
            .align(Alignment.CenterStart)
            .padding(start = 16.dp)
            .size(20.dp)
    )
    IconButton(
        modifier = Modifier
    ) {
        if (task.isDone.value) {
            Image(
                painter = painterResource(id = R.drawable.hanamaru),
                contentDescription = null,
                modifier = Modifier
                    .size(100.dp)
            )
        }
    }
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/a66d5198-041d-e6da-f99f-e23bb2a76f2a.gif">

こだわりポイントは、`Box`を使うことによって、手書きのように、四角い枠の上に重ねて花丸を表示したところです。

## 背景色をグラデーションにする
ここまでの作業で重要なポイントを逃していました。
「咲希ちゃんのイメージカラーが無い」
咲希ちゃんといえば、黄色とピンクのグラデーションになっている髪の毛です。
この色がとても可愛いんです。ということで、アプリの背景色として使いたいと思います。

```Kotlin
val Coral = Color(0xFFF3A397)
val LightYellow = Color(0xFFF8EE94)
val gradient = Brush.verticalGradient(
    colors = listOf(LightYellow, Coral),
)

Box(
    modifier = Modifier
        .background(brush = gradient)
        .fillMaxSize()
) {
}
```

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/f8a28a3e-6388-e5b5-638c-b8becdb0ba74.png">

`Brush`を使うことで、グラデーションを表現する事ができます。
いい感じに咲希ちゃんのイメージカラーに近づけて可愛くなったのではないでしょうか。

## Jetpack Composeで、画像がバウンドするアニメーションを表示
タスクを達成したときに、もっと祝ってもらうアニメーションがあったほうが可愛いと思うので、
最後に、タスクが達成されたときにクラッカーが飛び出すアニメーションを追加します。
完成したものは以下の通りです。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/4bad7ed0-c0ae-b145-5ead-0ad8c7c032ed.gif">

```Kotlin
IconButton(
    onClick = {
        task.isDone.value = !task.isDone.value
        showImage.value = true
        coroutineScope.launch {
            scale.animateTo(
                0.5f,
                animationSpec = tween(
                        durationMillis = 20 
                )
            )
            scale.animateTo(
                1f,
                animationSpec = spring( 
                    dampingRatio = Spring.DampingRatioMediumBouncy,
                    stiffness = Spring.StiffnessLow,
                )
            )
            showImage.value = false
        }
    }
) 
```
先程作ったアイコンボタンのonClickに、タスク達成時にのみ表示するように実装します。コルーチンを使って、`scale.animateTo()`を用いて画像の大きさを変更するアニメーションを実装します。

今回はバウンドを1回だけ行いたかったので、`scale.animateTo()`を2つ書きましたが、これを増やすことでバウンドの回数を増やすことも可能です。

また私事ですが、この実装はAndroid Studio に入れているGitHub Copllotの誘導でほとんど完成してしまいました。
アニメーションについては[公式ドキュメント](https://developer.android.com/jetpack/compose/animation/quick-guide?hl=ja)に詳しく載っているので、よく読んで理解することをおすすめします。

## まとめ
今回完成したものはこちらです。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286660/725a0d80-649e-b9dc-3711-8037758837ca.gif">

- アプリ内のフォントを変更する
- 文字にマーカーペン風アンダーラインを引く
- アイコンボタンの実装
- 背景色をグラデーションにする
- Jetpack Composeで画像がバウンドするアニメーションを表示する

今回この５つを実装したことによって、最終的に咲希ちゃんのノートのような可愛いアプリが完成しました。

しかし時間の都合上、やろうとしてできなかったこともあります。
もっと咲希ちゃんの可愛いノートに近づけれるように、以下のタスクを今後取り組んでいきたいと思います。
- ノートの表紙デザインを作成する
- タスクを達成した時に画面全体に花吹雪のアニメーションを付ける
- 入力画面をノート風にする
- 上記が完成したらアプリのリリース

これからも時間を作って、より可愛いアプリを目指していきたいと思います！

明日の [レコチョク Advent Calendar 2023](https://qiita.com/advent-calendar/2023/recochoku)  は23日目＜Jetpack Composeでスイカゲームは再現できる？＞です。お楽しみに！

## 参考文献
- [https://magazine.techacademy.jp/magazine/31841]( https://magazine.techacademy.jp/magazine/31841)
- [https://developer.android.com/jetpack/compose/animation/quick-guide?hl=ja]( https://developer.android.com/jetpack/compose/animation/quick-guide?hl=ja)
- [ https://qiita.com/Takarkiz/items/ceda050f0b63d122413e]( https://qiita.com/Takarkiz/items/ceda050f0b63d122413e)


この記事はレコチョクのエンジニアブログの記事を転載したものとなります。

https://techblog.recochoku.jp/10179