Jetpack Compose基礎

[FUN Advent Calendar 2019 Part1](https://adventar.org/calendars/4058)，14日目の記事です．

昨日はふぁるくんの[世界料理学会 in Hakodateに参戦してきた話](https://fal0164.hatenablog.com/?_ga=2.62405036.1478605853.1575213225-390479868.1569589324)でした．

今年はじめて[Advent Calendar](https://adventar.org/)というものに参加してみました．
テーマが自由だったので，好きなことを書きます．
色々迷いましたが，[某LT会での記事](https://qiita.com/kota_2402/items/7bbdd87be8024785e25b)の続き的なことを書こうと思いました．
某LT会から結構時間が経ったのですが，最近触れていなかったのでJetpack Composet周りを軽くまとめてみようかなと思いました

# 概要
以前記事を書いた時には，[AOSP（Android Open Source Project）](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/ui/README.md)からダウンロードして利用するしか方法がありませんでした．
しかし，まだプレアルファ版ですが，Android Studio 4.0からライブラリとして[Jetpack Compose](https://developer.android.com/jetpack/compose)が使用できるようになりました（プレアルファ版なので，**製品版には使用しないでください**という注意書きがあります）．
そこで，**Android Studio 4.0 Canary4**を利用してJetpack Composeを使用していこうと思います．
 ちゃんと書こうとするとModelやStateとかも出てきてしまうのですが，今回はシンプルなUI作成の紹介をしていきます．

## 余談
Android Studio4.0 Canary4のタイトル部分(?)がかっこよくなってますね．
Android Studio3.5から背景に模様が入っていましたがここまで変えてくるとは...

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/a046bc0f-4c64-c682-17df-6fd75373157c.png" width="400px">

# 導入
今回は，自分で新たにプロジェクト作成して行いました．
jetpack composeを自分のプロジェクトで利用する方法は[こちら](https://developer.android.com/jetpack/compose/setup)の手順で行いました．
~~現状では，プロジェクト作成時にjetpack composeを使うか否か選べないようです．
まだプレアルファ版なので無いだけで，そのうち追加されそうですね~~
自分がプロジェクト作成する際に見落としているだけでした．
画像のような「Empty Compose Activity」を選んでプロジェクトを作成すればできるっぽいです．

<img width="236" alt="スクリーンショット 2019-12-13 13.39.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/654363fc-0abf-b2ff-ee0e-08a6fac4b8c7.png">

# Let's try Jetpack Compose
基礎的なUIの表示方法を軽くまとめていきます．

## 前提
説明に入る前に...
下記のように`setContent`の中にUI部分を書いていきます．
今回のコードは全て，`setContent`の中に入れる関数として作成していきます．
※小見出しに従来のCompoment名で書いているのでご注意ください．

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            createGreetingUI()
        }
}
```

## TextView
やっぱり初めはJetpack Composeにご挨拶しなきゃですよね(笑)
てことで，文字列の表示から行っていきます．
`Text`というCompomentに，`text = {表示させたい文字列}`としてあげれば文字列が表示されます．

```kotlin
@Composable
fun createGreetingUI() {
    greetingView("JetpackCompose")
}

@Composable
fun greetingView(name: String) {
    MaterialTheme {
        Center {
            Text(text = "Hello $name!")
        }
    }
}
```
<img width=236" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/c22f7620-7475-a584-0d1f-59306c46c7b4.png">

このように`Hello JetpackCompose!`と表示されます．

この例では，`text`以外の設定をデフォルトから変更していません．
もちろんフォントやフォントサイズの変更もできます．
下のように，TextStyleを設定することでTextを好きなようにカスタマイズできます．

```kotlin
fun originalTextViewStyle() = TextStyle(color = Color.Red, fontSize = 30.sp, fontFamily = FontFamily.Cursive)
```

```kotlin
Text(text = "Hello $name!", style = originalTextViewStyle())
```

<img width=236" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/65e3e255-fbf4-e896-0bbf-efe6084672a5.png">


この例では．フォント，フォントサイズ，フォントカラーを変更しています．
今回はFontFamilyにもともと入っているフォントを使用していますが，各自で追加したものでもできます．

## ImageView
次は，画像の表示を行っていきます．
画像には，DraweImageを使用し，`image = {画像}`で表示させます．

```kotlin
@Composable
fun createImageUI() {
    MaterialTheme {
        Center {
            Container(height = 300.dp, width = 300.dp) {
                DrawImage(image = +imageResource(R.drawable.droidkun))
            }
        }
    }
}
```

<img width=236" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/ea7feb49-c66e-a9d3-0ee1-f1472a9d10e5.png">

実際に表示させるとこのような感じになります．
DrawImageのみだと画像のサイズ通りに表示されてしまうため，Containerで表示させる範囲を指定しています．

## EditText
従来とは，だいぶCompoment名が変わりましたが，`TextField`を使用します．

`value`部分は表示される文字列，`onValueChange`は入力された文字列となる．
そのため，`onValueChange`で`+state`関数に入力された文字列に更新し，`value`で`+state`関数を代入することで入力された文字列が更新されます．

```kotlin
@Composable
fun createEditTextUI() {
    val state = +state { "" }
    TextField(
        value = state.value,
        onValueChange = { state.value = it },
        editorStyle = EditorStyle(
            textStyle = TextStyle(
                fontSize = (20.sp)
            )
        )
    )
}
```

<img width=236" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/c2a3a9e5-2371-64fc-42e8-707caa0341a3.png">

このようになりました．
現状では従来の`Hint`はなく，下線が表示されていません．
`Hint`に関しては調べてみると無理やり実装している例も見つけましたが，そのうち追加されそうなので省きます．

従来通りキーボードの設定もできるので，今回は`NumberPassword`を試してみました．

```kotlin
keyboardType = KeyboardType.NumberPassword
```

<img width=236" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/b049cdd2-6082-66ef-05e4-adda0251a616.png">

あれっ！？
キーボードの設定をPasswordにしたら，表示される文字は`*`になるべきではないのかと思いました．
このままでは各自で実装する際に，変換する必要がありそうですが，きっと修正されますよね？

## AppBar
次に，AppBarを表示させていきます．
まず，既存の方法でデフォルトのActionBarを非表示にします．
下記のようにparentを`Theme.AppCompat.Light.NoActionBar`に変更します．

```xml:stymes.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
```
これで，ActionBarを非表示にできたので，AppBarを追加していきます．
AppBarには，TopAppBarを使用し，`icon = {アイコン}`と`title = {タイトル}`で表示できます．


```kotlin
@Composable
fun createAppBarUI() {
    MaterialTheme {
        Column(mainAxisAlignment = MainAxisAlignment.Start) {
            TopAppBar(
                title = { Text(text = "cuntomAppBar", style = originalAppBarTextStyle()) },
                color =  Color(0xFF339933),
                navigationIcon = {
                    VectorImageButton(R.drawable.ic_home) {
                        /**
                         * iconタップ処理
                         */ 
                    }
                }
            )
        }
    }
}

fun originalAppBarTextStyle() = TextStyle(color = Color.White)

@Composable
fun VectorImageButton(@DrawableRes id: Int, onClick: () -> Unit) {
    Ripple(bounded = false) {
        Clickable(onClick = onClick) {
            VectorImage(id)
        }
    }
}

@Composable
fun VectorImage(@DrawableRes id: Int, tint: Color = Color.Transparent) {
    val vector = +vectorResource(id)
    WithDensity {
        Container(
            width = vector.defaultWidth.toDp(),
            height = vector.defaultHeight.toDp()
        ) {
            DrawVector(vector, tint)
        }
    }
}
```
上記のコードではこのような表示になります．

<img width=236" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/a438a8c8-589f-f154-566a-8db3293dd68d.png">

上記コードのiconを表示させる部分では，VectorImageButtonでRippleとClickを付与し，VectorImageでVectorの範囲指定と表示を行っています．

使ってみた感じいくつか注意すべき点があるように感じました．

- `MaterialTheme`で囲まないと**No colors found!**と怒られます．  
これは，内部的には指定しているため，color指定するしないに関わらず怒られます．
- `Column`などで場所を指定したあげないとフルスクリーンで表示されます．
- ステータス部分の背景色は，`(この場合)AppTheme`の`colorPrimaryDark`が自動で設定されます．  
しかし，AppBarの背景色は`colorPrimary`が設定されずデフォルトの<font color="6200EE">**0xFF6200EE**</font>が設定されます．  
指定しない場合，`colorPrimary`がAppBarの背景色に設定されればスッキリするように感じました．

## ListView
何か配列のものをリスト状に表示させることはよくあることです．
しかし，現状ではCompomentとして用意されていないようです．
そのため，ListVIew的なもののを作成してみます．

フルーツの配列を作ってリスト状に表示させていきます．
ただColumに追加していってもスクロールできず，下の方が見えなくなるため`VerticalScroller`の中に作っていきます．
さらに，Listの要素分だけ`ListItem`を追加することで，リスト状に表示できます．
区切り線が必要な場合には，`Divider`で表示させてください．

```kotlin
@Composable
fun createListUI(context: Context) {

    val fruitList = arrayOf("りんご", "いちご", "みかん", "なし", "パイナップル", "スイカ", "メロン", "さくらんぼ", "バナナ", "キウイ", "ぶどう", "レモン", "グレープフルーツ", "モモ", "ゆず")

    MaterialTheme {
        FlexColumn {
            flexible(flex = 1f) {
                VerticalScroller {
                    Column {
                        for (fruit in fruitList) {
                            ListItem(
                                text = fruit,
                                onClick = {
                                    Toast.makeText(context, fruit, Toast.LENGTH_SHORT).show()
                                }
                            )
                            /**
                             * 区切り線の表示
                             */
                            Divider(color = Color(0x44666666))
                        }
                    }
                }
            }
        }
    }
}

```
<img width=236" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225369/ee869489-93d7-74d8-b574-8a55dfb7165f.png">

今回は，文字列のみのリストですが`ListItem`では，iconなども設定できるっぽいのでもう少しちゃんとしたものも作れそうです．

# まとめ
この場で全てを紹介するなんてのは無理なので開発の中でよく使いそうなものの一部を紹介しました．
まだ，未完成の状態なので今後の追加次第で今回紹介したものもより応用を効かすことができそうです．

Jetpack Compose自体はReact, Flutterなどを触ったことがある人ならある程度理解しやすいのかなと思います．
自分はあまり触ったことがないので慣れるまでは苦労しそうだなと思いました．
もう少し色々試してみようと思ってはいたのですが，時間がなくあまり試せませんでした．
やることが落ち着いたらPart2でも書こうかなと思います．

### Jetpack Compose用に作成した個人のリポジトリ
下記のリンクに今回紹介したコードを含めたアプリのレポジトリを載せました．
よかったら参考程度にでも.....
https://github.com/mattukouta/JetpackCompose

# 参考サイト
- Use Android Studio with Jetpack Compose : https://developer.android.com/jetpack/compose/setup
- Tutorial Jetpack Compose Basics : https://developer.android.com/jetpack/compose/tutorial
- Jetpack ComposeでListViewっぽいの作ってみた : https://qiita.com/Yu-shun/items/d24514d9b196ce00830b
- compose-samples : https://github.com/android/compose-samples