湯婆婆ネタが流行っているなーと眺めていたら、Jetpack Compose 版がないじゃないかと思ったので、作ってみました。
(Jetpack Compose 湯婆婆!!って飛んでいきそうな勢いがあって好きです。作中でも時々飛んでいましたっけ?)
元ネタ:Javaで湯婆婆を実装してみる
そもそも Jetpack Composeって?
Jetpack Compose は、Android のネイティブ UI を構築するための最新のツールキットです。
細かい説明はこちらをどうぞ
開発環境
- Jetpack Compose は
Android Studio 4.2 Preview版
で開発できます。 - まだアルファ版ですので、本格的な開発には向かないですが、今回のようなネタ開発にはちょうど良いお題
コード
処理はMainActivity
と YubabaViewModel
に分かれています。
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ではなく、
Text
、TextField
、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を利用して伝えています。
うごきはこんな感じ
- 0文字入力時には、しっかり落ちます
感想
- 実はちゃんと Jetpack Compose にさわってアプリっぽく作ったのは初めてな気がします。
- 状態を持たないという特性を理解するのにちょっと苦労しました!
- まだまだ Jetpack Compose はα版ですので、いろいろと変わるかなーとおもっていますが、なかなかおもしろかったです
追記(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
}
}