17
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Jetpack Compose 湯婆婆!!

Last updated at Posted at 2020-11-11

湯婆婆ネタが流行っているなーと眺めていたら、Jetpack Compose 版がないじゃないかと思ったので、作ってみました。
(Jetpack Compose 湯婆婆!!って飛んでいきそうな勢いがあって好きです。作中でも時々飛んでいましたっけ?)

元ネタ:Javaで湯婆婆を実装してみる


そもそも Jetpack Composeって?

Jetpack Compose は、Android のネイティブ UI を構築するための最新のツールキットです。

細かい説明はこちらをどうぞ

開発環境

  • Jetpack Compose は Android Studio 4.2 Preview版で開発できます。
  • まだアルファ版ですので、本格的な開発には向かないですが、今回のようなネタ開発にはちょうど良いお題:point_up:

コード

処理はMainActivityYubabaViewModelに分かれています。

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            YubabaAppTheme {
                Surface(color = MaterialTheme.colors.background) {
                    YubabaTalk()
                }
            }
        }
    }
}

@Composable
fun YubabaTalk(yubabaViewModel: YubabaViewModel = viewModel()){

    val isSend: Boolean by yubabaViewModel.sendState.observeAsState(initial = false)
    val inputName: String by yubabaViewModel.inputName.observeAsState("")
    val yubabaNaming: String by yubabaViewModel.yubabaNaming.observeAsState(initial = "")

    Column(
        modifier = Modifier.padding(16.dp),
    ) {
        Text(
            text = "契約書だよ。そこに名前を書きな。",
            style = typography.h5,
            modifier = Modifier.padding(bottom = 8.dp)
        )

        val textState = remember { mutableStateOf(TextFieldValue()) }
        TextField(
            value = textState.value,
            onValueChange = {
                if (!isSend) {
                    textState.value = it
                }
            },
            backgroundColor = if (isSend) Color.Transparent else Color.DarkGray,
            modifier = Modifier.padding(bottom = 8.dp)
        )

        when {
            !isSend -> {
                Button(
                    onClick = {
                        yubabaViewModel.onSendStateChanged(true)
                        yubabaViewModel.onNameSend(textState.value.text)
                    },
                    modifier = Modifier.padding(bottom = 8.dp)
                ) {
                    Text("名前を教える")
                }
            }
            isSend -> {
                Text(
                    text = "フン。${inputName}というのかい。贅沢な名だねぇ。",
                    style = typography.h5,
                    modifier = Modifier.padding(bottom = 8.dp)
                )
                Text(
                    text = "今からお前の名前は${yubabaNaming}だ。いいかい、${yubabaNaming}だよ。分かったら返事をするんだ、${yubabaNaming}!!",
                    style = typography.h4
                )
            }
        }
    }
}
YubabaViewModel.kt
class YubabaViewModel: ViewModel() {

    private val _sendState = MutableLiveData(false)
    val sendState: LiveData<Boolean> = _sendState

    private val _inputName = MutableLiveData("")
    val inputName: LiveData<String> = _inputName

    private val _yubabaNaming = MutableLiveData("")
    val yubabaNaming: LiveData<String> = _yubabaNaming

    fun onSendStateChanged(sendState: Boolean) {
        _sendState.value = sendState
    }
    fun onNameSend(name: String) {
        _inputName.value = name
        val newNameIndex: Int = Random.nextInt(name.length)
        val editName = name.substring(newNameIndex, newNameIndex + 1)
        _yubabaNaming.value = editName
    }
}

解説

MainActivity

  • Android ですのでActivityのライフサイクルに合わせた実装になります。
  • setContentブロックの中で、表示するUIの構築を実施します。
    • UIの構築はコンポーズ可能な関数を用います。
  • @Composableアノテーションをメソッドに付与する事で、その中のUIが構築される動きになります。
  • UIの書き方は Android 特有の XMLではなく、TextTextField ButtonなどComposeUIを呼び出して登録していきます。
  • Columnはそのまま列を構築するComposeUIです。(ListViewやGridView的なイメージのもの)
  • ComposeUIの多くは状態を持たない特性があるので、今の状態なんかはViewModelやプロパティーで保持しています。
    • textState は常にテキストの入力値を持っています。
    • isSend は名前を教えたかの状態
    • inputName は入力した名前
    • yubabaNaming は湯婆婆に決めてもらった名前
  • LiveDataの状態を ComposeUI へ反映させるため、observeAsState(初期値)を利用しています。下のライブラリを取り込んでおいてください。
build.gradle
dependencies {
    implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
}

YubabaViewModel

  • Android のandroidx.lifecycle.ViewModel()を元にしたViewModelで、保持している値はLiveDataを利用して伝えています。

うごきはこんな感じ

device-2020-11-12-011022.gif

  • 0文字入力時には、しっかり落ちます:sweat:

感想

  • 実はちゃんと Jetpack Compose にさわってアプリっぽく作ったのは初めてな気がします。
  • 状態を持たないという特性を理解するのにちょっと苦労しました!
  • まだまだ Jetpack Compose はα版ですので、いろいろと変わるかなーとおもっていますが、なかなかおもしろかったです:blush:

追記(2020/11/12)

  • Jetpack Compose のViewModelからの購読処理にはMutableStateもCompose用に用意されていますので、MutableState版も差分箇所だけ載せておきます。
MainActivity.kt

@Composable
fun YubabaTalk(yubabaViewModel: YubabaViewModel = viewModel()){

    val isSend: Boolean = yubabaViewModel.sendState
    val inputName: String = yubabaViewModel.inputName
    val yubabaNaming: String = yubabaViewModel.yubabaNaming

    //・・・省略
}
YubabaViewModel.kt
class YubabaViewModel: ViewModel() {

    var sendState: Boolean by mutableStateOf(false)
        private set
    var inputName: String by mutableStateOf("")
        private set
    var yubabaNaming: String by mutableStateOf("")
        private set

    fun onSendStateChanged(sendState: Boolean) {
        this.sendState = sendState
    }
    fun onNameSend(name: String) {
        this.inputName = name
        val newNameIndex: Int = Random.nextInt(name.length)
        val editName = name.substring(newNameIndex, newNameIndex + 1)
        yubabaNaming = editName
    }
}

  • 'LiveData'特有の遠回りなデータ更新で書かなくて済むのはいい感じですね!
  • ただ、通常のViewレイアウト実装の場合には使えなくなるらしいので、注意も必要らしいです。
  • CodeLabにあった、どっちだよ感が:sweat_smile:
    codelab_image.png
17
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?