参考にした場所
引用:UIと状態
スターター コードを取得を入手する。
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
$ cd basic-android-kotlin-compose-training-tip-calculator
$ git checkout starter
入手したら展開して起動します。
チップアプリで用いる単語をstring.xmlに登録します。
res -> values -> string.xml
<resources>
<string name="app_name">Tip Time</string>
<string name="calculate_tip">Calculate Tip</string>
<string name="bill_amount">Bill Amount</string>
<string name="tip_amount">Tip Amount: %s</string>
</resources>
テキストフィールドを作る。
スターターコードはある程度書いてあるので、その次からやっていきます。
テキストフィールドコンポーザブルを作ります。
@Composable
fun EditNumberField(modifier: Modifier=Modifier){
TextField(value = "", onValueChange = {},modifier=modifier)
}
これをTipTimeLayout()に入れます。
Text(…)
EditNumberField(modifier = Modifier
.padding(bottom = 36.dp)
.fillMaxWidth())
Text(…)
Composeで状態を使用する。
状態とは時間と共に変化する可能性があるものを指します。
テキストフィールドの状態を最初0にします。
@Composable
fun EditNumberField(modifier: Modifier=Modifier){
val amountInput = "0"
TextField(value = amountInput , onValueChange = {},modifier=modifier)
}
構成
変化する可能性のある値を監視するためにmutableStateOf()関数を使います。
また警告が出るために無視します。
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
@SuppressLint("UnrememberedMutableState")
@Composable
fun EditNumberField(modifier: Modifier=Modifier){
var amountInput = mutableStateOf("0")
TextField(value = amountInput.value,
onValueChange = {amountInput.value = it},
modifier=modifier)
}
onValueChange コールバックはテキストボックスへの入力が変更されるとトリガーされます。
この状態でアプリを起動して、テキストフィールドに入力しても文字は保存されません
remember 関数を使用して状態を保存する
再コンポーズにより、コンポーズ可能なメソッドが何度も呼び出される場合があります。コンポーザブルは、保存されていない場合、再コンポーズ中に状態をリセットします。
そのため、保存する値がある場合、一時的に格納する必要があります。
remember関数を使います。
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
TextField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = modifier
)
}
これで保存されるようになりました。
外観を変更する
テキスト ボックスにラベルを追加する
追加するのはラベル、下線、キーボード タイプの制限です。
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
TextField(
value = amountInput,
onValueChange = { amountInput = it },
singleLine = true,
label = { Text(stringResource(R.string.bill_amount)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = modifier
)
}
チップの金額を表示する
スターター コードの一部として提供されている private calculateTip()を用います。
テキストフィールドに入力された値はStringなのでDoubleに変更します。
使うのは toDoubleOrNull 関数です。
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
TextField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = modifier,
singleLine = true,
label = { Text(text = stringResource(id = R.string.bill_amount))},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
次にここで計算されたtipを表示する必要があります。
このコンポーザブルの値を別のコンポーザブルから巻き上げ、ホイスティングします。
入力したと同時に行う、つまりステートレスにもします。
状態ホイスティング
状態を他のコンポーザブルで利用できるするためにvalue パラメータと onValueChange パラメータを追加して状態をホイスティングします。
@Composable
fun EditNumberField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
//...
TextField(
value = value,
onValueChange = onValueChange,
//...
)
そしてremember で保存した状態を EditNumberField() 関数から TipTimeLayout() 関数に移動します。
@Composable
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
Column(
//...
) {
//...
}
}
状態ホイスティングにより、テキストフィールドに入力された値が、すべてつながるようになりました。
状態ホイスティングを行うために、コンポーズ可能な関数 EditNumberField() に amountInput 値と、ユーザーの入力によって amountInput 値を更新するラムダ コールバックの 2 つの引数を渡しました。
完成がこちらです
@Composable
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount)
Column(
modifier = Modifier
.statusBarsPadding()
.padding(horizontal = 40.dp)
.verticalScroll(rememberScrollState())
.safeDrawingPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.calculate_tip),
modifier = Modifier
.padding(bottom = 16.dp, top = 40.dp)
.align(alignment = Alignment.Start)
)
EditNumberField(
value = amountInput,
onValueChange = { amountInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
Text(
text = stringResource(R.string.tip_amount, tip),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.height(150.dp))
}
}
@Composable
fun EditNumberField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
value = value,
onValueChange = onValueChange,
singleLine = true,
label = { Text(stringResource(R.string.bill_amount)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = modifier
)
}