Jetpack Compose日記#2

## 欲しいコンポーザブルや機能の探し方
##### referenceを見る
- androidx.compose.* の[Top-level functions summary](https://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#top-level-functions-summary)
- androidx.compose.materialの[Components](https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#components)

##### accompanistにないか調べる
JetpackComposeの便利ライブラリ集 → [github](https://github.com/google/accompanist)

##### kotlin Slackのcomposeチャンネルで検索する
例
キーボードが表示されたときに、フォーカスされているTextFieldがキーボードの下に隠れないように自動でスクロールする方法の場合。
↓
scroll focusで検索
↓
RelocationRequesterというのがあるらしい

##### Android Code Searchで検索する
- [Android Code Search](https://cs.android.com/search?q=RelacotionRequester&ss=androidx%2Fplatform%2Fframeworks%2Fsupport)にて、kotlin slackやstack overflowで見つけた機能のサンプル等を探す。

##### JetpackCompose Roadmapをチェックする
- 探している機能がなかった時に活用。今後の実装予定が公開されている。
- 自分で実装するか、用意されるまで待つかの判断材料になる
[loadmap](https://developer.android.com/jetpack/androidx/compose-roadmap?hl=ja)

## 完成目標
<img src="https://developer.android.com/static/codelabs/jetpack-compose-layouts/img/c31e78e48cc1f336_1920.png?hl=ja" width="200px">

##### 検索バー
検索バーを実装するには、[テキストフィールド](https://material.io/components/text-fields)と呼ばれるマテリアル コンポーネントを使用します。

```
    TextField(value = "", onValueChange = {}, modifier = modifier)
```
実装結果は以下
![Image](https://developer.android.com/static/codelabs/jetpack-compose-layouts/img/c2e1eec30f36bc72_1920.png?hl=ja)
***修飾子*** を利用して次のことを行う。([修飾子一覧](https://developer.android.com/jetpack/compose/modifiers-list?hl=ja))

- コンポーザブルのサイズ、レイアウト、動作、外観を変更する
- ユーザー補助ラベルなどの情報を追加する
- ユーザー入力を処理する
- 要素をクリック可能、スクロール可能、ドラッグ可能、ズーム可能にするなど、高レベルの操作を追加する

呼び出す各コンポーザブルには ***modifier*** パラメータがあり、コンポーザブルの外観や動作に適応するように設定できます。この修飾子を設定すると、複数の修飾子メソッドを連結して、より複雑な適応を構築できます。

以下は、高さの最小値、横幅一杯にサイズを取る、アイコン、背景色、プレスホルダー設定。
```
TextField(
        value = "",
        modifier = modifier
            .fillMaxWidth()
            .heightIn(min = 56.dp),
        onValueChange = {},
        leadingIcon = {  //Icon
            Icon(
                imageVector = Icons.Default.Search,
                contentDescription = null
            )
        },
        colors = TextFieldDefaults.textFieldColors(backgroundColor = MaterialTheme.colors.surface),  //背景色
        placeholder = {  //プレスホルダー
            Text(stringResource(id = R.string.placeholder_search))
        }
    )
```
##### 位置揃え
以下のデザインを元に実装する。
![イメージ](https://developer.android.com/static/codelabs/jetpack-compose-layouts/img/9d11e16a8817686f_1920.png?hl=ja)

注:テキストの***ベースライン***とは、文字が「乗っている」ラインのことです。

```
@Composable
fun AlignYourBodyElement(
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
   ) {
       Image(
           painter = painterResource(R.drawable.ab1_inversions),
           contentDescription = null
       )
       Text(
           text = stringResource(R.string.ab1_inversions)
       )
   }
}
```
size 修飾子および clip 修飾子と contentScale パラメータを使用して、Image コンポーザブルを調整する。
- size 修飾子は、前のステップで確認した fillMaxWidth 修飾子および heightIn 修飾子と同様に、特定のサイズに合うようにコンポーザブルを調整します。
- clip 修飾子の動作は異なり、コンポーザブルの外観を調整します。任意の Shape に設定すると、コンポーザブルのコンテンツがその形状にクリップされます。
- 画像を適切なサイズに変更する必要もあります。これを行うには、Image の contentScale パラメータを使用します。いくつかのオプションがあります。最も一般的なものを以下に示します。
![image](https://developer.android.com/static/codelabs/jetpack-compose-layouts/img/5f17f07fcd0f1dc_1920.png?hl=ja)
```
Image(
            painter = painterResource(id = R.drawable.ab1_inversions),
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier
                .size(88.dp)
                .clip(CircleShape)
        )
```
一般に、コンポーザブルを親コンテナ内で***位置揃え***するには、親コンテナの位置揃えを設定します。つまり、子自体に親内部での配置について指示する代わりに、子の配置方法を親に指示します。
![image](https://camo.qiitausercontent.com/61d1d57a07226e73a8c0d85e38a166e89fa70541/68747470733a2f2f646576656c6f7065722e616e64726f69642e636f6d2f7374617469632f636f64656c6162732f6a65747061636b2d636f6d706f73652d6261736963732f696d672f353138646266616432336565316230355f313932302e706e673f686c3d6a61)

Column は、子を水平方向に配置する方法を決定します。オプションは次のとおりです。
- Start
- CenterHorizontally
- End

Row は、垂直方向の配置を設定します。オプションは Column の場合と類似しています。
- Top
- CenterVertically
- Bottom

Box は、水平方向と垂直方向の両方の配置を結合します。オプションは次のとおりです。
- TopStart
- TopCenter
- TopEnd
- CenterStart
- Center
- CenterEnd
- BottomStart
- BottomCenter
- BottomEnd

コンテナのすべての子も、これと同じ配置パターンに従います。1 つの子の位置をオーバーライドするには、対象の子に align 修飾子を追加します。

以下はテキストを水平方向に中央揃えで配置する。
```
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier)
```
最後にテキストの調整をする。
```
Text(
    text = stringResource(id = text),
    style = MaterialTheme.typography.h3,  
    modifier = Modifier.paddingFromBaseline(top = 24.dp, bottom = 8.dp) //ベースラインが基準
)
```
##### スロットAPI
![image](https://developer.android.com/static/codelabs/jetpack-compose-layouts/img/2c0bc456d50bb078_1920.png?hl=ja)

各セクションには***タイトル***と***スロット***があります。スロットには、セクションに応じてさまざまなコンテンツを動的に挿入できるようになっています。この柔軟なセクション コンテナを実装するには、いわゆる[スロットAPI](https://developer.android.com/jetpack/compose/layouts/basics?hl=ja#slot-based-layouts)を使用します。

***スロットベースのレイアウト***: デベロッパーが自由にコンテンツを挿入できる空のスペースを UI に用意します。これを使用することで、より柔軟なレイアウトを作成できます。

```
@Composable
fun HomeSection(
    @StringRes title: Int,
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Column(modifier) {
        Text(
            style = MaterialTheme.typography.h2,
            text = stringResource(id = title).uppercase(Locale.getDefault()),   //全大文字変換
            modifier = Modifier.paddingFromBaseline(top = 40.dp, bottom = 8.dp).padding(horizontal = 16.dp) //左右均等に16dp
        )
        content
    }
} 

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun HomeSectionPreview() {
   MySootheTheme {
       HomeSection(R.string.align_your_body) {
           AlignYourBodyRow()  //コンテンツ指定
       }
   }
}
```
コンポーザブルのスロットには、content パラメータを使用できます。このように、HomeSection コンポーザブルを使用する場合は、後置ラムダを使用してコンテンツ スロットを埋めることができます。実装は以下のようにレイアウトされる。
![image](https://developer.android.com/static/codelabs/jetpack-compose-layouts/img/d824b60e650deeb_1920.png?hl=ja)

##### ホーム画面-スクロール
![image](https://developer.android.com/static/codelabs/jetpack-compose-layouts/img/a535e10437e9df31_1920.png?hl=ja)
検索バーと、その下に 2 つのセクションを単純に配置します。全体がデザインどおりに収まるように、スペースを追加する必要があります。これまで使用していなかったコンポーザブルとして、Spacer があります。これにより、Column 内にスペースを配置できます。代わりに Column のパディングを設定すると、先ほどの Favorite Collections グリッドで確認されたのと同じように、端のアイテムにクリッピングが生じてしまいます。

```
@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
    Column(modifier) {
        Spacer(modifier = Modifier.height(16.dp))   //スペースの追加
        SearchBar(Modifier.padding(horizontal = 16.dp))
        HomeSection(title = R.string.align_your_body) {
            AlignYourBodyRow()
        }
        HomeSection(title = R.string.favorite_collections) {
            FavoriteCollectionsGrid()
        }
        Spacer(modifier = Modifier.height(16.dp))
    }
}
```
このデザインはほとんどのデバイスサイズにフィットしますが、横表示にした場合など、デバイスの高さが十分でないときは、垂直方向のスクロールが必要になります。そのためには、スクロール動作を追加する必要があります。
- 前述のように、LazyRow や LazyHorizontalGrid などの Lazy レイアウトは自動的にスクロール動作を追加します。ただし、常に Lazy レイアウトが必要になるわけではありません。一般に、***リスト内に多数の要素がある場合や、大規模なデータセットを読み込む場合は、Lazy レイアウトを使用します***。そのため、すべてのアイテムを一度に出力すると、パフォーマンス コストが増大し、アプリの速度が低下します。リストの要素の数が限られている場合は、代わりにシンプルな Column または Row を使用して、***手動でスクロール動作を追加できます***。そのためには、verticalScroll 修飾子または horizontalScroll 修飾子を使用します。これには、スクロール状態を外部から変更するために使用される、スクロールの現在の状態を含む ScrollState が必要です。この例ではスクロール状態を変更するわけではないため、rememberScrollState を使用して永続的な ScrollState インスタンスを作成します。

```
Column(modifier = modifier
        .verticalScroll(rememberScrollState())
        .padding(vertical = 16.dp)) {
       ...
    }
```

コンポーザブルのスクロール動作を検証するには、プレビューの高さを制限し、インタラクティブプレビューで実行します。

```
//高さを制限
@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2, heightDp = 180)
@Composable
fun ScreenContentPreview() {
   MySootheTheme { HomeScreen() }
}
```

##### ボトムナビゲーション-マテリアル
![image](https://developer.android.com/static/codelabs/jetpack-compose-layouts/img/2f42ad2b882885f8_1920.png?hl=ja)
Compose マテリアル ライブラリの一部である BottomNavigation コンポーザブルを使用できます。BottomNavigation コンポーザブル内に 1 つ以上の BottomNavigationItem 要素を追加すると、マテリアル ライブラリによって自動的にスタイルが設定されます。

```
@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
    BottomNavigation(modifier, backgroundColor = MaterialTheme.colors.background) {     //背景色の指定
        BottomNavigationItem(icon = {   //アイテム1
            Icon(
                imageVector = Icons.Default.Spa,
                contentDescription = null
            )
        },
        selected = true,
        onClick = {})
        BottomNavigationItem(icon = {   //アイテム2
            Icon(
                imageVector = Icons.Default.AccountCircle,
                contentDescription = null
            )
        },
        label = {
            Text(stringResource(id = R.string.bottom_navigation_profile))
        },
        selected = false,
        onClick = {}
        )
    }
}
```
##### Scaffold
Scaffold を使用すると、マテリアル デザインを実装するアプリ向けに構成可能なトップレベルのコンポーザブルを利用できます。これには、マテリアル コンセプトに対応したさまざまなスロットがあります（その一つは下部のバーです）。この下部のバーに、前のステップで作成したボトム ナビゲーション コンポーザブルを配置できます。

```
@Composable
fun MySootheApp() {
    MySootheTheme {
        //paddingをコンテンツのルートに適用することで、バーを適切にオフセットする。
        Scaffold(bottomBar = { SootheBottomNavigation() }) { padding ->
            HomeScreen(Modifier.padding(padding))
        }
    }
}
```