Infinito Nirone 7

白羽の矢を刺すスタイル

Jetpack Compose TextField の横幅を n 文字分だけ確保したい

View の仕組みでは、 TextView のプロパティとして ems が定義してあり、これを利用するとTextView の横幅を ems で指定した文字数分の横幅にしてくれます。

R.attr  |  Android Developers

Jetpack Compose では Text や TextField にそのものズバリな引数や Modifier はないので、自分で計算します。

TextMeasurer で横幅を計算する

TextMeasurer はその名の通り文字を描画するのに必要な領域を計算するクラスです。テキストスタイルや親コンポーネントの縦横の大きさを制約として与えることで、テキストが領域内に収まり切るかどうかも計算可能です。

developer.android.com

今回は TextField の横幅を常に一定に保つため、あらかじめダミーの文字列を用意して TextMeasurer に食わせて横幅を計算します。これでダミーの文字列の長さの分だけ横幅を確保することになります。 次の例では数字6文字分の横幅を計算します。

// 数字6文字分を TextField の横幅にするときの、数字6文字分の横幅を計算
val measurer = rememberTextMeasurer()
val measureResult = measurer.measure(
  text = "000000",
  maxLines = 1,
)

TextField の PaddingValues を求める

TextField は内部で PaddingValues を設定しており、自動でスペースをつけています。 このスペースの大きさは TextField のスタイルによって異なります。 OutlinedTextField ならば outlinedTextFieldPadding() で、TextField の場合は label があるときは textFieldWithLabelPadding()、label がないときは textFieldWithoutLabelPadding() が標準の PaddingValues です。

TextMeasurer で計算した横幅と PaddingValues の水平方向の padding を足し合わせる

TextMeasurer で計算した横幅と PaddingValue の水平方向の padding を足し合わせると、TextField の横幅が計算できます。 TextMeasurer で計算した横幅は px なのに対し、PaddingValues が持つスペースは dp を単位にしているので、TextMeasurer で計算した横幅を dp になおす必要があります。

val measurer = rememberTextMeasurer()
val measureResult = measurer.measure(
  text = "000000",
  maxLines = 1,
)
val padding = textFieldWithoutLabelPadding()

TextField(
  modifier = Modifier.width(
    with(LocalDensity.current) {
      measureResult.size.width.toDp() + padding.calculateStartPadding(LocalLayoutDirection.current) + padding.calculateEndPadding(LocalLayoutDirection.current)
    }
  )
)

注意点

今回は数字のみで文字列の横幅を計算しましたが、全角文字を入れたりするとまた計算が変わってきそうです。 TextMeasure#measure はフォントの種類や文字の大きさなども考慮して横幅を計算できますが、今回のように与えられた文字数に必要な横幅を計算するために予め決め打ちした文字で計算すると、実際に入力した文字によってはどうしてもズレが出ます。