Google DeveloversでJetpackComposeの基礎を学んでみた

## 初めに
初投稿です。勉強の記録としてアウトプットしたく、Qiitaを始めました。

初投稿はKotlinのJetpackComposeについて書きたいと思います。
Kotlinは勉強して1年ぐらいで、アプリを1つ作成したことがある程度です。

## JetpackComposeとは
>Jetpack Compose は、ネイティブ UI を構築するための Android の推奨最新ツールキットです。AndroidでのUI開発を簡素化し、加速します。少ないコード、強力なツール、直感的な Kotlin API を使用して、アプリをすばやく実現できます。

（[公式サイト](https://developer.android.com/compose "公式サイト")から引用）
XMLを使用せずにUIを作成することができて、少ないコードで自由度が高いデザインを作成することがでるツールキットなのかと。。

## JetpackComposeを勉強しようと思った経緯
前述で述べたアプリは初めて作成したということもありUIをxmlで作成していました。UIを作成するだけなのに思ったより時間がかかってしまったり、ConstraintLayoutの設定がよくわからなかったり、デザインの自由度がなかったり、、、といろいろ感じながら実装していました。。
そして今回新たにアプリを作成しようと考えている際にxml以外のUI実装について調べると、JetpackComposeの存在を知り勉強しようと思いました。

最初は基礎ということでGoogle Develoversの[Jetpack Compose の基本](https://developer.android.com/codelabs/jetpack-compose-basics?hl=ja&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fcompose%3Fhl%3Dja%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fjetpack-compose-basics#0)を一通り学んだことをまとめていきたいと思います！

## 学んだ内容
### 1.Composeableについて
```
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier
    )
}
```
通常の関数に@Composableのアノテーションをつけており、順番に@Composableを呼ぶ。

Composableの呼び出しはActivityのonCreate()などで行う。
```
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                Scaffold( modifier = Modifier.fillMaxSize() ) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}
```
setContent : Composableの呼び出しを開始する。
Scaffold : Composerの一種で複雑なユーザーインターフェースに標準化されたプラットフォームを提供する基本的な構造を提供してくれるAPI。（[公式サイト](https://developer.android.com/develop/ui/compose/components/scaffold?hl=ja "公式サイト")）

### 2.Previewについて
Composableを使用するとビルドしなくてもUIの表示、更新をリアルタイムで反映して確認することが出来る。（[公式サイト](https://developer.android.com/develop/ui/compose/tooling/previews?hl=ja "公式サイト")）
Previewを表示するためには`@Preview`のアノテーションをつけた別のComposableを作成する。
```
@Composable
fun SimpleComposable() {
    Text("Hello World")
}

@Preview(showBackground = true)
@Composable
fun SimpleComposablePreview() {
    SimpleComposable()
}
```
以下を押下で確認することが出来る。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093477/f5d095bc-441e-4cf9-89e7-4b732c5e6615.png">

また、UIの右上の指マーク（Interactive Mode）を押下すると実際にUIを操作することが出来る。ボタン押下で遷移などの挙動もビルドせずに確認することが出来る。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4093477/a5cb6940-0616-42c5-a250-24e9b1eb1d82.png">

### 3.マテリアル・スタイルについて
* マテリアルについて
デザインを簡単に実装できる。また、テーマ設定やコンポーネントなどを提供している。（[公式サイト](https://developer.android.com/develop/ui/compose/designsystems?hl=ja&_gl=1*331yr5*_up*MQ..*_ga*NzYyNzgwOTA1LjE3NDgyNDE4NDM.*_ga_6HH9YJMN9M*czE3NDgyNDE4NDIkbzEkZzAkdDE3NDgyNDE4NDIkajAkbDAkaDE2Njg2MDg1NjQkZEpITDhFWUxYekIxWnZMM0RiX0Y1MUhrazIwS01wanZpc0E."公式サイト")）

例えばボタンのデザイン
昇降ボタンを作成したいときには以下のように書くだけでUIを作成できる。
```
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
    Elevatedbutton(onClick = onIncrement) { // 昇降ボタン
        Text("Clicked $count times")
    }
}

```
ほかにも以下のデザインのボタンを簡単に設定することが出来る。（[公式サイト](https://m3.material.io/components/buttons/overview#d3da4750-ed9c-4241-a17d-81c3c5ebb0ca"公式サイト")）
> * Filled button　（塗りつぶしボタン）
> * Filled tonal button　（塗りつぶし同系色ボタン）
> * Outlined button　（アウトライン・ボタン）
> * Text button　（テキスト・ボタン）

* デザインについて
    * スクロール
        * `LazyColumn`を使用するのがよい。 `LazyColumn` は画面に表示されているアイテムのみをレンダリングするため、大きなリストを表示する場合は効率が良くなる。
        ※`LazyColumn` と `LazyRow` は、Android ビュー内の `RecyclerView` と同等。
        <br>

    * 文字の大きさ
        * `MaterialTheme.typography` を使用。
        マテリアルで定義されているテキスト スタイル（displayLarge, headlineMedium, titleSmall, bodyLarge, labelMedium など）にアクセスできる。
        ```
        Text(
            text = name, 
            style = MaterialTheme.typography.headlineMedium
        )
        ```
        また、copy関数を定義すると、定義済みのスタイルを変更できる。
        ```
        Text(
            text = name,
            style = MaterialTheme.typography.headlineMedium.copy(
                fontWeight = FontWeight.ExtraBold // 文字を極太に
            )
        )
        ```
        
### 4.状態の保存について
* Compoableの状態の保存
State と MutableStateは、なんらかの値を保持し、その値が変化するたびに UI の更新（再コンポジション）をトリガーするインターフェース。これがないとCompose はそれを「状態変更」として検出しないため、何も起こらない。
再コンポジションの前後で状態を保持するには、`remember` を使用して可変状態を「記憶」します。
<details><summary>サンプルコード</summary>

```
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    val expanded = remember { mutableStateOf(false) }　//ボタンの状態の保存
    val extraPadding = if (expanded.value) 48.dp else 0.dp
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(
                modifier = Modifier
                    .weight(1f)
                    .padding(bottom = extraPadding)
            ) {
                Text(text = "Hello ")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { expanded.value = !expanded.value }
            ) {
                Text(if (expanded.value) "Show less" else "Show more")
            }
        }
    }
}
```
</details>

* 状態ホイスティング
状態ホイスティングとは、Jetpack Compose において 状態をコンポーザブルの呼び出し元に移動し、コンポーザブル自体をステートレスにする プログラミングパターン。
通常、Composable内で`remember { mutableStateOf(...) }` を使って状態を管理できるが、Composableを状態を内部に持つと、以下のマイナスな面がある。
    - **外部から状態を変更できない** → 柔軟性が低くなる
    - **テストが難しくなる** → 内部状態を直接変更できないため
    - **再利用性が低くなる** → 状態を持つコンポーザブルは特定の用途に限定される

これらの問題を解決し、状態ホイスティングを使用することでComposableの **再利用性** や **テストのしやすさ** を向上させることができる。

<details><summary>ボタンの状態を保存したサンプルコード</summary>

状態ではなく関数を OnboardingScreen に渡すことで、このコンポーザブルを再利用しやすくし、他のComposableによって変更されないように状態を保護する。
```
@Composable
fun MyApp(modifier: Modifier = Modifier) {

    var shouldShowOnboarding by remember { mutableStateOf(true) }

    Surface(modifier) {
        if (shouldShowOnboarding) {
            OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
        } else {
            Greetings()
        }
    }
}

@Composable
fun OnboardingScreen(
    onContinueClicked: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Welcome to the Basics Codelab!")
        Button(
            modifier = Modifier
                .padding(vertical = 24.dp),
            onClick = onContinueClicked
        ) {
            Text("Continue")
        }
    }

}
```
</details>

* プロセス終了後の状態の維持
プロセス終了時に状態を破棄したくない場合は`rememberSaveable`を使う。そうすることで個々の状態が保存され、構成の変更（回転など）やプロセスの終了後も保持される。

### 5.アニメーションについて
`animateDpAsState` Composableを使用する。この関数は State オブジェクトを返すが、このオブジェクトの value はアニメーションが終了するまで継続的に更新される。
アニメーションをカスタマイズする`animationSpec` パラメータ（省略可能）もある。
```
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {

    var expanded by remember { mutableStateOf(false) }

    val extraPadding by animateDpAsState(  //アニメーションの設定
        if (expanded) 48.dp else 0.dp,
        animationSpec = spring(　// ばねのアニメーション
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(
                modifier = Modifier
                    .weight(1f)
                    .padding(bottom = extraPadding.coerceAtLeast(0.dp))
            ) {
                Text(text = "Hello, ")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { expanded = !expanded }
            ) {
                Text(if (expanded) "Show less" else "Show more")
            }
        }
    }
}
```
spring : 時間に関連するパラメータは受け取らず、物理的な特性（減衰と剛性）に基づいて、より自然なアニメーションを実現する。

## 最後に
今回JetpackComposeの基礎を学んでみましたが、使いこなすにはもう少し勉強が必要だと感じました。
一番いいと思った点はPreviewやInteractive ModeでビルドしなくてもUIを確認できる点です。結構ビルドが手間だったりするのでこれだけでもJetpackComposeを使う意味があると思いました。

まだまだGoogle DeveloversにはJetpackComposについて学べる教材が提供されているため、今後も勉強していきたいと思います。


初めての投稿ということもあり、読みにくい部分あるにも関わらず最後まで目を通してくださりありがとうございました。