Jetpack Compose入門(12) TextFieldで文字入力欄を作る



前回のボタンクリックに続いて、今回もユーザー操作を受け取って表示を更新する処理を見ていきましょう。今回は文字入力欄を作ります。文字入力欄は、TextFieldまたはOutlinedTextFieldを使って作成します。

TextFieldとOutlinedTextField

文字入力欄を作るためのコンポーネントも、他の多くのコンポーネントと同じくandroidx.compose.materialパッケージに定義されています。TextFieldOutlinedTextFieldという見た目の異なる2種類の文字入力欄のコンポーネントが用意されています。名前から想像すると、TextFieldのほうがノーマルな文字入力欄っぽいのですが、実際はOutlinedTextFieldの方がシンプルな見た目になっています。TextFieldの方は、OutlinedTextFieldよりも見た目が強調され、文字入力欄であることが分かりやすいようになっています。

定義はほとんど同じです。違うのは最後の2つ、shapecolorsだけです。shapecolorsのcompanion propertyを定義しておけば、関数定義を分けるほどでもないのでは?という気もしますが、、、まあせっかく2種類あるので、場面に応じて使い分けましょう。Developer Guide曰く、いろんなコンテンツに囲まれている文字入力欄は、目立たせるためにTextFieldを使い、文字入力欄がたくさん並んでいるようなUIでは、うるさくならないようにOutlinedTextFieldを使いましょう、とのことです。

@Composable
fun TextField(
    value: String?,
    onValueChange: ((String) -> Unit)?,
    modifier: Modifier? = Modifier,
    enabled: Boolean? = true,
    readOnly: Boolean? = false,
    textStyle: TextStyle? = LocalTextStyle.current,
    label: (@Composable () -> Unit)? = null,
    placeholder: (@Composable () -> Unit)? = null,
    leadingIcon: (@Composable () -> Unit)? = null,
    trailingIcon: (@Composable () -> Unit)? = null,
    isError: Boolean? = false,
    visualTransformation: VisualTransformation? = VisualTransformation.None,
    keyboardOptions: KeyboardOptions? = KeyboardOptions.Default,
    keyboardActions: KeyboardActions? = KeyboardActions(),
    singleLine: Boolean? = false,
    maxLines: Int? = Int.MAX_VALUE,
    interactionSource: MutableInteractionSource? = remember { MutableInteractionSource() },
    shape: Shape? = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
    colors: TextFieldColors? = TextFieldDefaults.textFieldColors()
): Unit
@Composable
fun OutlinedTextField(
    value: String?,
    onValueChange: ((String) -> Unit)?,
    modifier: Modifier? = Modifier,
    enabled: Boolean? = true,
    readOnly: Boolean? = false,
    textStyle: TextStyle? = LocalTextStyle.current,
    label: (@Composable () -> Unit)? = null,
    placeholder: (@Composable () -> Unit)? = null,
    leadingIcon: (@Composable () -> Unit)? = null,
    trailingIcon: (@Composable () -> Unit)? = null,
    isError: Boolean? = false,
    visualTransformation: VisualTransformation? = VisualTransformation.None,
    keyboardOptions: KeyboardOptions? = KeyboardOptions.Default,
    keyboardActions: KeyboardActions? = KeyboardActions.Default,
    singleLine: Boolean? = false,
    maxLines: Int? = Int.MAX_VALUE,
    interactionSource: MutableInteractionSource? = remember { MutableInteractionSource() },
    shape: Shape? = MaterialTheme.shapes.small,
    colors: TextFieldColors? = TextFieldDefaults.outlinedTextFieldColors()
): Unit

文字入力を反映させる

ここからはOutlinedTextFieldを使って説明していきますが、TextFieldでも同じです。

実際にOutlinedTextFieldを使って文字入力欄を作っていきます。ソースコードはこんな感じです。

Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.TopCenter
) {
    var text by remember { mutableStateOf("") }
    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        modifier = Modifier.padding(20.dp)
    )
}

ボタンクリックでUIを更新する」の回で、by remember { mutableStateOf() }で変数を宣言することによって変数の更新をJetpack Composeが監視し、更新を検出するとUIが再構築され、表示に反映されると学びました。TextFieldでもやり方は同じです。

文字入力を扱うので、String型の変数が必要です。mutableStateOf("")で空の文字列を使って初期化しているので、textString型の変数になります。

OutlinedTextField()value変数は表示する文字列を指定する引数です。空文字で初期化したtextを渡しているので、初期状態では空欄になります。空文字以外で初期化すれば、最初から文字が入力された状態で表示されます。

onValueChange引数は、ユーザーの文字入力により値が変化したときに呼び出されるコールバック関数です。コールバック関数の引数は変化後の文字列です。text = itで変化後の文字列をtextに代入しています。(Kotlinではラムダ式の引数定義を省略した場合、itで引数にアクセスできます。)するとJetpack Composeがtextの変化を検出し、UIを再構築し、TextFieldを再描画します。この一連の処理の結果、ユーザーが入力した文字が入力欄に表示されます。

カスタマイズ

入力欄の説明(labelとplaceholder)

label引数は、入力欄の説明です。コンポーザブル関数で指定します。単純な文字列で事足りることがほとんどだと思いますが、その場合はラムダ内にText()を書けばよいです。labelで指定した文字列は、TextFieldに文字が入力されていない場合は入力欄に表示され、文字が入力されると入力欄の左上に小さく表示されます。

Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.TopCenter
) {
    var text by remember { mutableStateOf("") }
    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        modifier = Modifier.padding(20.dp),
        label = { Text(text = "Label") }
    )
}

placeholderは、TextFieldが空欄の時に表示する入力例です。lebelと同様にコンポーザブル関数を渡しますが、多くの場合はTextでよいと思います。placeholderの場合はlabelと違って、文字を入力すると消えます。

Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.TopCenter
) {
    var text by remember { mutableStateOf("") }
    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        modifier = Modifier.padding(20.dp),
        placeholder = { Text(text = "Placeholder") }
    )
}

labelplaceholderを両方指定すると、TextFieldにフォーカスが当たっていなくて文字が入力されていない場合はlabelが入力欄に表示され、フォーカスが当たるとlabelは小さくなってplaceholderが表示され、文字を入力するとplaceholderが消えます。個人的にはせわしない感じがしてしまうので、labelplaceholderのどちらかを目的に合わせて使うほうがいいかなと思います。

Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.TopCenter
) {
    var text by remember { mutableStateOf("") }
    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        modifier = Modifier.padding(20.dp),
        label = { Text(text = "Label") },
        placeholder = { Text(text = "Placeholder")}
    )
}

改行の有無(singleLine)

singleLine引数は、改行を許可するかどうかを指定します。デフォルトではfalseです。

falseの場合、複数行の入力が可能になります。右端まで到達すると自動で改行されます。また、キーボードに改行キーが表示され、手動で改行することもできます。TextFieldの高さは、modifier等で指定していない場合は、入力文字の行数に合わせて自動的に調整されます。TextFieldのサイズを制限する必要がある場合は、maxLines引数と組み合わせて使います。ただしmaxLines引数はTextFieldの表示行数を制限するだけで、入力自体は何行でもできてしまいます。maxLinesを指定している場合や、modifier等で高さを指定している場合で、入力行数が表示可能な行数を超えた場合は、スクロールします。

Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.TopCenter
) {
    var text by remember { mutableStateOf("one two three four five") }
    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        modifier = Modifier.padding(20.dp).width(200.dp),
        singleLine = false,
        maxLines = 5
    )
}

trueの場合、1行しか入力ができません。右端まで到達すると、スクロールします。キーボードの改行キーは表示されません。maxLines引数は無効になります。

Box(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.TopCenter
) {
    var text by remember { mutableStateOf("one two three four ") }
    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        modifier = Modifier.padding(20.dp).width(200.dp),
        singleLine = true
    )
}

まとめ

今回はTextFieldOutlinedTextFieldを使って文字入力欄を作る方法を説明しました。文字が入力されたことを検出して表示に反映させる方法や、ラベルやプレースホルダーの設定方法、入力欄の行数の設定方法についても説明しました。


Jetpack Compose入門