テキスト フィールドを構成する

TextField を使用すると、ユーザーがテキストの入力や変更を行えるようになります。使用できるテキスト フィールドには、状態ベースのテキスト フィールド値ベースのテキスト フィールドの 2 種類があります。コンテンツを表示するタイプを選択します。

TextField の状態を管理するうえで、より完全で信頼性の高いアプローチを提供するため、状態ベースのテキスト フィールドを使用することをおすすめします。次の表に、これらのテキスト フィールドの種類の違いと、状態ベースのテキスト フィールドの主な利点を示します。

機能

値に基づくテキスト フィールド

状態ベースのテキスト フィールド

状態ベースのメリット

状態管理

onValueChange コールバックでテキスト フィールドの状態を更新します。onValueChange によってレポートされた変更に基づいて、独自のステートの value を更新する必要があります。

TextFieldState オブジェクトを明示的に使用して、テキスト入力の状態(値、選択、コンポジション)を管理します。この状態は保存して共有できます。

  • onValueChange コールバックが削除され、非同期動作を導入できなくなりました。
  • この状態は、再コンポーズ、構成、プロセスの終了後も保持されます。

ビジュアル変換

表示されるテキストの見た目を変更するために VisualTransformation を使用します。通常、入力と出力の両方の形式を 1 つのステップで処理します。

InputTransformation は、ユーザーの入力が状態にコミットされる前に変更するために使用され、OutputTransformation は、基盤となる状態データを変更せずにテキスト フィールドの内容をフォーマットするために使用されます。

  • OutputTransformation を使用して、元の未加工テキストと変換後のテキスト間のオフセット マッピングを指定する必要がなくなりました。

行数上限

singleLine: Boolean, maxLines: IntminLines: Int を受け入れて、行数を制御します。

lineLimits: TextFieldLineLimits を使用して、テキスト フィールドが占有できる行数の最小値と最大値を構成します。

  • TextFieldLineLimits 型の lineLimits パラメータを指定することで、行数の上限を設定する際の曖昧さを解消します。

保護されたテキスト フィールド

なし

SecureTextField は、パスワード フィールドを記述するために状態ベースのテキスト フィールドの上に構築されたコンポーザブルです。

  • セキュリティを最適化でき、textObfuscationMode を使用した事前定義済みの UI が付属しています。

このページでは、TextField を実装し、TextField 入力をスタイル設定し、キーボード オプションやユーザー入力の視覚的な変換など、他の TextField オプションを構成する方法について説明します。

TextField 実装を選択する

TextField の実装には次の 2 つのレベルがあります。

  1. TextField はマテリアル デザインの実装であり、次のようなマテリアル デザイン ガイドラインに沿っているため、この実装を選択することをおすすめします。
  2. BasicTextField を使用すると、ユーザーはハードウェア キーボードまたはソフトウェア キーボードを使用してテキストを編集できます。ただし、ヒントやプレースホルダのような装飾は用意されていません。

TextField(
    state = rememberTextFieldState(initialText = "Hello"),
    label = { Text("Label") }
)

「Hello」という単語が入力された編集可能なテキスト フィールド。

OutlinedTextField(
    state = rememberTextFieldState(),
    label = { Text("Label") }
)

紫色の枠線とラベルが付いた編集可能なテキスト フィールド。

スタイルTextField

TextFieldBasicTextField は、カスタマイズ用のパラメータを多数共有しています。TextField の完全なリストについては、TextField ソースコードをご覧ください。有用なパラメータの一部を以下に示します。

  • textStyle
  • lineLimits

TextField(
    state = rememberTextFieldState("Hello\nWorld\nInvisible"),
    lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 2),
    placeholder = { Text("") },
    textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
    label = { Text("Enter text") },
    modifier = Modifier.padding(20.dp)
)

ラベルと 2 つの編集可能な行がある、複数行の TextField

デザインにマテリアルの TextField または OutlinedTextField が必要な場合は、BasicTextField ではなく TextField をおすすめします。ただし、マテリアル仕様の装飾を必要としないデザインを構築する場合には、BasicTextField を使用する必要があります。

Brush API で入力をスタイル設定する

TextField でより高度なスタイル設定を行うには、Brush API を使用します。次のセクションでは、ブラシを使用して TextField 入力にカラー グラデーションを追加する方法について説明します。

Brush API を使用してテキストのスタイルを設定する方法については、Brush API を使用して高度なスタイル設定を有効にするをご覧ください。

TextStyle を使用して色付きグラデーションを実装する

TextField 内で入力時に色付きのグラデーションを実装するには、任意のブラシを TextFieldTextStyle として設定します。この例では、linearGradient を含む組み込みブラシを使用して、TextField にテキストを入力すると、虹色のグラデーション効果が表示されるようにします。

val brush = remember {
    Brush.linearGradient(
        colors = listOf(Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Magenta)
    )
}
TextField(
    state = rememberTextFieldState(), textStyle = TextStyle(brush = brush)
)

buildAnnotatedString と SpanStyle、linearGradient を使用して、テキストの一部のみをカスタマイズする。
図 1. TextField コンテンツのレインボー グラデーション効果。

テキスト フィールドの状態を管理する

TextField は、コンテンツと現在の選択用に TextFieldState という専用の状態保持クラスを使用します。TextFieldState は、アーキテクチャに適合する場所にホイストされるように設計されています。TextFieldState には、主に次の 2 つのプロパティがあります。

  • initialText: TextField の内容。
  • initialSelection: カーソルまたは選択が現在ある場所を示します。

TextFieldStateonValueChange コールバックなどの他のアプローチと異なる点は、TextFieldState が入力フロー全体を完全にカプセル化していることです。これには、正しいバッキング データ構造の使用、フィルタとフォーマッタのインライン化、さまざまなソースからのすべての編集の同期が含まれます。

TextFieldState() を使用して、TextField で状態をホイスティングできます。これには、rememberTextFieldState() 関数を使用することをおすすめします。rememberTextFieldState() は、コンポーザブルに TextFieldState インスタンスを作成し、状態オブジェクトが記憶されるようにして、組み込みの保存と復元機能を提供します。

val usernameState = rememberTextFieldState()
TextField(
    state = usernameState,
    lineLimits = TextFieldLineLimits.SingleLine,
    placeholder = { Text("Enter Username") }
)

rememberTextFieldState には、空白のパラメータを指定するか、初期化時にテキストの値を表す初期値を渡すことができます。後続の再コンポーズで別の値が渡された場合、状態の値は更新されません。初期化後に状態を更新するには、TextFieldState で編集メソッドを呼び出します。

TextField(
    state = rememberTextFieldState(initialText = "Username"),
    lineLimits = TextFieldLineLimits.SingleLine,
)

テキスト フィールド内に「Username」というテキストが表示された TextField。
図 2. TextField。初期テキストは「Username」です。

TextFieldBuffer でテキストを変更する

TextFieldBuffer は、StringBuilder と同様の機能を持つ編集可能なテキスト コンテナとして機能します。テキスト コンテンツと現在の選択に関する情報を保持します。

TextFieldBuffer は、TextFieldState.editInputTransformation.transformInputOutputTransformation.transformOutput などの関数のレシーバ スコープとしてよく使用されます。これらの関数では、必要に応じて TextFieldBuffer を読み取り、更新できます。その後、これらの変更は TextFieldState にコミットされるか、OutputTransformation の場合はレンダリング パイプラインに渡されます。

appendinsertreplacedelete などの標準の編集関数を使用して、バッファの内容を変更できます。選択状態を変更するには、selection: TextRange 変数を直接設定するか、placeCursorAtEndselectAll などのユーティリティ関数を使用します。選択自体は TextRange で表されます。開始インデックスは含まれ、終了インデックスは含まれません。(3, 3) のように開始値と終了値が同じ TextRange は、現在選択されている文字がないカーソル位置を示します。

val phoneNumberState = rememberTextFieldState()

LaunchedEffect(phoneNumberState) {
    phoneNumberState.edit { // TextFieldBuffer scope
        append("123456789")
    }
}

TextField(
    state = phoneNumberState,
    inputTransformation = InputTransformation { // TextFieldBuffer scope
        if (asCharSequence().isDigitsOnly()) {
            revertAllChanges()
        }
    },
    outputTransformation = OutputTransformation {
        if (length > 0) insert(0, "(")
        if (length > 4) insert(4, ")")
        if (length > 8) insert(8, "-")
    }
)

TextFieldState でテキストを編集する

状態変数を介して状態を直接編集できる方法はいくつかあります。

  • edit: 状態の内容を編集し、TextFieldBuffer 関数を提供します。これにより、insertreplaceappend などのメソッドを使用できます。

    val usernameState = rememberTextFieldState("I love Android")
    // textFieldState.text : I love Android
    // textFieldState.selection: TextRange(14, 14)
    usernameState.edit { insert(14, "!") }
    // textFieldState.text : I love Android!
    // textFieldState.selection: TextRange(15, 15)
    usernameState.edit { replace(7, 14, "Compose") }
    // textFieldState.text : I love Compose!
    // textFieldState.selection: TextRange(15, 15)
    usernameState.edit { append("!!!") }
    // textFieldState.text : I love Compose!!!!
    // textFieldState.selection: TextRange(18, 18)
    usernameState.edit { selectAll() }
    // textFieldState.text : I love Compose!!!!
    // textFieldState.selection: TextRange(0, 18)

  • setTextAndPlaceCursorAtEnd: 現在のテキストをクリアし、指定されたテキストに置き換えて、カーソルを末尾に設定します。

    usernameState.setTextAndPlaceCursorAtEnd("I really love Android")
    // textFieldState.text : I really love Android
    // textFieldState.selection : TextRange(21, 21)

  • clearText: すべてのテキストをクリアします。

    usernameState.clearText()
    // textFieldState.text :
    // textFieldState.selection : TextRange(0, 0)

他の TextFieldState 関数については、TextFieldState リファレンスをご覧ください。

ユーザー入力を変更する

以降のセクションでは、ユーザー入力を変更する方法について説明します。入力変換では、ユーザーが入力している間に TextField 入力をフィルタリングできます。一方、出力変換では、ユーザー入力が画面に表示される前にフォーマットできます。

入力変換でユーザー入力をフィルタする

入力変換を使用すると、ユーザーからの入力をフィルタできます。たとえば、TextField が米国の電話番号を受け取る場合、10 桁のみを受け取るようにします。InputTransformation の結果は TextFieldState に保存されます。

一般的な InputTransformation ユースケース用の組み込みフィルタがあります。長さを制限するには、InputTransformation.maxLength() を呼び出します。

TextField(
    state = rememberTextFieldState(),
    lineLimits = TextFieldLineLimits.SingleLine,
    inputTransformation = InputTransformation.maxLength(10)
)

カスタム入力変換

InputTransformation は単一の関数インターフェースです。カスタム InputTransformation を実装する場合は、TextFieldBuffer.transformInput をオーバーライドする必要があります。

class CustomInputTransformation : InputTransformation {
    override fun TextFieldBuffer.transformInput() {
    }
}

電話番号の場合、TextField に数字のみを入力できるようにするカスタム入力変換を追加します。

class DigitOnlyInputTransformation : InputTransformation {
    override fun TextFieldBuffer.transformInput() {
        if (!TextUtils.isDigitsOnly(asCharSequence())) {
            revertAllChanges()
        }
    }
}

入力変換をチェーンする

テキスト入力に複数のフィルタを追加するには、then 拡張関数を使用して InputTransformation を連結します。フィルタは順番に実行されます。ベスト プラクティスとして、最も選択性の高いフィルタを最初に適用して、最終的にフィルタリングされるデータに対する不要な変換を回避します。

TextField(
    state = rememberTextFieldState(),
    inputTransformation = InputTransformation.maxLength(6)
        .then(CustomInputTransformation()),
)

入力変換を追加すると、TextField 入力は最大 10 桁を受け入れます。

表示前に形式を設定する

OutputTransformation を使用すると、ユーザー入力が画面にレンダリングされる前にフォーマットできます。InputTransformation とは異なり、OutputTransformation を使用して行った書式設定は TextFieldState に保存されません。前の電話番号の例を基に、適切な場所に丸かっこやハイフンを追加する必要があります。

アメリカの電話番号。かっこ、ダッシュ、対応するインデックスで正しくフォーマットされています。
図 3. 適切な形式と対応するインデックスを持つ米国の電話番号。

これは、値ベースの TextFieldVisualTransformation を処理する更新された方法です。主な違いは、オフセット マッピングを計算する必要がないことです。

OutputTransformation は単一抽象メソッド インターフェースです。カスタム OutputTransformation を実装するには、transformOutput メソッドをオーバーライドする必要があります。

class CustomOutputTransformation : OutputTransformation {
    override fun TextFieldBuffer.transformOutput() {
    }
}

電話番号の形式を設定するには、OutputTransformation にインデックス 0 の開きかっこ、インデックス 4 の閉じかっこ、インデックス 8 のハイフンを追加します。

class PhoneNumberOutputTransformation : OutputTransformation {
    override fun TextFieldBuffer.transformOutput() {
        if (length > 0) insert(0, "(")
        if (length > 4) insert(4, ")")
        if (length > 8) insert(8, "-")
    }
}

次に、OutputTransformationTextField に追加します。

TextField(
    state = rememberTextFieldState(),
    outputTransformation = PhoneNumberOutputTransformation()
)

変換の連携の仕組み

次の図は、テキスト入力から変換、出力までのフローを示しています。

テキスト入力がテキスト出力になるまでの変換の視覚化。
図 4. テキスト入力が変換を経てテキスト出力になるまでの流れを示す図。
  1. 入力ソースから入力が受信されます。
  2. 入力は InputTransformation を通してフィルタされ、TextFieldState に保存されます。
  3. 入力は、フォーマットのために OutputTransformation を通して渡されます。
  4. 入力は TextField に表示されます。

キーボード オプションを設定する

TextField を使用すると、キーボード レイアウトなどのキーボード構成オプションを設定できます。また、キーボードでサポートされている場合は、自動修正を有効にすることも可能です。ソフトウェア キーボードが次に示すオプションに対応していない場合、一部のオプションは保証されない可能性があります。サポートされているキーボード オプションのリストを次に示します。

  • capitalization
  • autoCorrect
  • keyboardType
  • imeAction

参考情報