Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

Android Jetpack Compose 独自 TextField 実装まとめ

こんにちは。Android アプリ開発担当の nagayama(@nagayan_dev)です。
今回は Jetpack Compose での独自 TextField の実装についてまとめましたので、その内容をお伝えします。



背景

私が担当している「auサービスToday」で Jetpack Compose に置き換える対応を着々と進めています。記事検索画面の対応をしている時に、文字入力部分でハマった部分があるため、それをまとめていきます。

Material3 のライブラリでは既に SearchBar が存在します。「auサービスToday」ではまだ Material3 の導入をしていないため、代わりに TextField を用いて独自実装を行います。

developer.android.com

実装

TextField をそのまま利用すると、下記のような実装となります。

@Composable
fun ArticleSearchBar(〜省略〜) {
  TextField(
    value = query,
    modifier = Modifier
      .fillMaxWidth()
      .height(48.dp),
    onValueChange = onQueryChanged,
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
    keyboardActions = KeyboardActions(
    onSearch = { onSearch.invoke()},),
    leadingIcon = {
      IconButton(onClick = onClickBack) {
        Icon(Icons.Rounded.ArrowBack, 〜省略〜)
      }
    },
    trailingIcon = {
      IconButton(onClick = onClickClose) {
        Icon(Icons.Rounded.Close,〜省略〜)
      }
    }
  )
}

今回は TextField の左側に Back ボタンを、右側にテキストを削除する Close ボタンを表示します。 また、keyboardOptions / keyboardActions でキーボードを検索ボタンにすることができます。



こちらの実装だけでは目的のデザインを実現することができなかったため、ここから下記の 4 点について対応・解説をしていきます。

  1. 文字の見切れと不要な下線の対応
  2. ヒント文字を表示する
  3. 入力文字やカーソル色を変更
  4. 画面を表示した時にフォーカスを当て、検索実行をした時にフォーカスを外す

1. 文字の見切れと不要な下線の対応

TextField は残念ながら固定で padding が設定されてしまっています。今回の記事検索画面のバーの高さが固定になっている影響で、デザインした padding よりも大きいサイズが設定されているため、文字が見切れてしまっていました。また表示されている下線も同様で、TextField を使用するとセットで付いてきてしまいます。

こちらを解消するため TextField ではなく、その元となっている BasicTextField とその中身をアレンジするための TextFieldDecorationBox を使用します。置き換えた場合のコードは下記になります。

@Composable
fun ArticleSearchBar(〜省略〜) {
  BasicTextField(
    value = query,
    modifier = Modifier
      .fillMaxWidth()
      .height(48.dp),
    onValueChange = onQueryChanged,
    keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
    keyboardActions = KeyboardActions(onSearch = { onSearch.invoke() }),
    decorationBox = @Composable { innerTextField ->
      TextFieldDefaults.TextFieldDecorationBox(
        value = query,
        innerTextField = innerTextField,
        enabled = true,
        singleLine = true,
        visualTransformation = visualTransformation,
        interactionSource = interactionSource,
        leadingIcon = {
          IconButton(onClick = onClickBack) {
            Icon(Icons.Rounded.ArrowBack, 〜省略〜)
          }
        },
        trailingIcon = {
          IconButton(onClick = onClickClose) {
            Icon(Icons.Rounded.Close, 〜省略〜)
          }
        },
        contentPadding = TextFieldDefaults.textFieldWithLabelPadding(0.dp, 0.dp, 0.dp, 0.dp)
      )
    }
  )
}

一番最後の contentPadding で padding の設定をしています。TextField ではここで固定の padding を指定していたため、スペースが表示されていました。



2. ヒント文字を表示する

BasicTextField を用いた場合のヒント文字を表示するためには、TextFieldDecorationBox の placeholder に Text を指定します。

TextFieldDefaults.TextFieldDecorationBox(
  〜省略〜
  placeholder = {
    Text(
      text = "ここに文字を入力",
      color = Color.Gray,
    )
  },
)



3. 入力文字やカーソル色を変更

文字フォントを変更したい場合は BasicTextField の引数の textStyle を変更します。 またカーソル色を変更したい場合は cursorBrush を変更します。

@Composable
fun ArticleSearchBar(〜省略〜) {
  BasicTextField(
    〜省略〜
    textStyle = LocalTextStyle.current.merge(TextStyle(Color.Red)),
    cursorBrush = SolidColor(Color.Blue),
  )


分かりやすいように赤文字、青カーソルと設定してみました

4. TextField にフォーカスを当てる/外す

最後にユーザービリティの観点から、

  • 画面を開いた時に自動で TextField にフォーカスが当てる
  • キーボードから検索を実行した時、自動でフォーカスを外す

をやっていきたいと思います。

フォーカスを当てる場合は FocusRequester 、フォーカスを外す場合は FocusManager を使用します。

@Composable
fun ArticleSearchBar(〜省略〜) {
  val focusRequester = remember { FocusRequester() }
  val focusManager = LocalFocusManager.current
  BasicTextField(
    modifier = 〜省略〜
     .focusRequester(focusRequester),
    keyboardActions = KeyboardActions(onSearch = {
      focusManager.clearFocus()
      onSearch.invoke()
    }),
    〜省略〜
  )
  SideEffect {
    focusRequester.requestFocus()
  }
}

FocusRequester はフォーカスを当てたい Compose の Modifier#focusRequester に設定し、requestFocus でフォーカスを当てる実行を行います。SideEffect の中で行うことで 画面が表示された = Compose が完了した 時に、フォーカスを当てることができます。

FocusManager は clearFocus を実行することで、任意のタイミングでフォーカスを外すことができます。

以上で実装は完了です。



まとめ

Jetpack Compose の独自 TextField の実装をまとめてみました。以前の SearchView と同様に一癖も二癖もあるものですが、一つ一つ対処方法はありそうなので粘り強く取り組みましょう。これからも Jetpack Compose ライフを楽しみましょう。