📝

JetpackCompose TextFieldについて色々(文字数制限とかも)

2021/09/01に公開
2

ソースコード

@Composable
fun TextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    readOnly: Boolean = false,
    textStyle: TextStyle = LocalTextStyle.current,
    label: () -> Unit = null,
    placeholder: () -> Unit = null,
    leadingIcon: () -> Unit = null,
    trailingIcon: () -> 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()
): @Composable Unit

引数はこの様になっており、色々とカスタムできる様になっている。

例えば中央表示にしたい場合は、

textStyle: にTextAlign.Centerにで中央表示することができる。

状態の変更

composeは基本的に状態を持っていないので、

Valueに変更を通知して状態を変えてあげないと、

状態が追えないのでTextFieldがずっと空白のままになる。

mutableStateで変更可能な状態を作り、

後はなんかややこしいことしなければならない雰囲気である。

状態ホイスティングを使う

流石JetpackComposeというところで、

実はめちゃくちゃ簡単に変更を通知できる。

それが状態ホイスティングである。

var name by rememberSaveable { mutableStateOf("") }

で作ったnameをvalue:に入れ、

onValueChange:に{ name = it }を渡してあげると勝手にやってくれる。

すごく便利。

@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.h5
        )
        OutlinedTextField(
            value = name,
            onValueChange = onNameChange,
            label = { Text("Name") }
        )
    }
}

こんな感じ。

現状maxLengthがない(まじで????)

まじです。

忘れてない???大丈夫????って感じなのですが、

ないものはしょうがないので何かしら対応をしなければなりません.......

maxLengthの打開策

@Composable
fun PinCodeField() {
    var text by remember { mutableStateOf("") }
    OutlinedTextField(
        value = text,
        onValueChange = { text = maxLength(it, 1) }
    )
}

private fun maxLength(input: String, maxLength: Int) = if (input.length > 1) input.substring(0,maxLength) else input

inputに対してsubstringで制御するという形を現状取っています。

onValueChange に作った関数maxLength(it, 制御したい数)という形。

この方法だけでは日本語入力の場合、文字数を超えたら入力中の文字がリセットされてしまう

OutlinedTextField(
    modifier = modifier,
    value = name,
    onValueChange = {
      if (it.length < 5)
        name = it
    },

日本語機能付きのキーボードは変換が確定するまでは

文字入力が確定してないような挙動になってしまうので

制限している文字数を超えてしまうと

入力中の文字が消えてしまうような挙動になる。

解決方法

var lengthIsOverMaxLength by remember { mutableStateOf(false) }
  val view = LocalView.current

  OutlinedTextField(
    modifier = modifier,
    value = creatorComment.value,
    onValueChange = valueChange@{
      when (lengthIsOverMaxLength) {
        true -> {
          if (it.length <= 140) {
            lengthIsOverMaxLength = false
            creatorComment.value = it
          }
          return@valueChange
        }
        false -> {
          if (it.length > 140) {
            lengthIsOverMaxLength = true
            // Composeのバグで、文字数制限に達したところで全て文字がクリアされてしまうため、エンターキーイベント発生させる
            view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER))
            view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER))
            return@valueChange
          }
          creatorComment.value = it
        }
      }
    }

dispatchKeyEventで無理矢理エンターを押すことで回避することができる。

このバグ認知してたんですが書くの忘れてましたホントゴメンナサイ

なにやらこのバグをもっといい方法で解決できるらしい

https://at-sushi.work/blog/49/

こちらの記事で詳しく解説してくださっています。

とても参考になるのでこちらの方法をとるのがいいと思います。

参考記事

https://developer.android.com/jetpack/compose/text?hl=ja

https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#TextField(kotlin.String,kotlin.Function1,androidx.compose.ui.Modifier,kotlin.Boolean,kotlin.Boolean,androidx.compose.ui.text.TextStyle,kotlin.Function0,kotlin.Function0,kotlin.Function0,kotlin.Function0,kotlin.Boolean,androidx.compose.ui.text.input.VisualTransformation,androidx.compose.foundation.text.KeyboardOptions,androidx.compose.foundation.text.KeyboardActions,kotlin.Boolean,kotlin.Int,androidx.compose.foundation.interaction.MutableInteractionSource,androidx.compose.ui.graphics.Shape,androidx.compose.material.TextFieldColors)

https://developer.android.com/jetpack/compose/state?hl=ja#state-hoisting「

http://y-anz-m.blogspot.com/2021/04/jetpack-compose-basictextfield.html

http://www.chansek.com/maxLength-alternative-jetpack-compose/

https://at-sushi.work/blog/49/

https://stackoverflow.com/questions/65780722/jetpack-compose-how-to-remove-edittext-textfield-underline-and-keep-cursor

デザイン参考

https://pratikchauhan11.medium.com/playing-with-textfield-in-compose-android-declarative-ui-de8c03aa4748

Discussion

anveloperanveloper

一つ質問させていただいてもよろしいでしょうか?

この方法だけでは日本語入力の場合、文字数を超えたら入力中の文字がリセットされてしまう

上記のことですが、FlutterのIOSでも起きるのです。
回避方法をご存じでしょうか?

いきなり質問、すみません。

あっぷる中谷あっぷる中谷

上記のことですが、FlutterのIOSでも起きるのです。
回避方法をご存じでしょうか?

FlutterのiOSだけというのが不可解ですが
とりあえず各ライブラリの最新版を使う
それでも直らなければ
この記事のようにどこで消えているかを特定する必要があるので
恐らくProviderもしくはRiverPodで状態管理をされているでしょうから
そちらの更新メソッドでブレイクポイントをかけてどのタイミングまで生存しているかを確認し、
それがわかれば死なないようにするという感じでしょうか