EditTextに入力したテキストを動的に変更したい~数字を自動的にカンマ区切りにしてみる~

EditTextで文字列を入力した際、入力した文字列の内容に応じて動的にテキストの内容を変更したい時ってありますよね。
そんな時は、TextWatcherを使えば実現できます。

## TextWatcherってなに
TextWatcherはテキスト入力の監視を行うためのリスナーです。
EditText#addTextChangedListener()でTextWatcherをリスナーとして登録することで、
EditTextのテキストの入力を検知し、動的にテキストの内容を変更することが可能です。


## コードサンプル
とりあえず、コードサンプルを作ったので見てみましょう。
今回は、数値を入力した際に自動でカンマ区切りの表示にする機能を実装してみました。


### MyTextWatcher.kt
今回は、MyTextWatcherという名前でTextWatcherインタフェースを実装したクラスを作成しました。


```kotlin:MyTextWatcher.kt
import android.text.Editable
import android.text.TextWatcher
import java.text.NumberFormat

class MyTextWatcher constructor(private val callback: Callback?) : TextWatcher {

    private val numberFormat: NumberFormat = NumberFormat.getInstance()
    //半角数値(0～9)以外にマッチする正規表現
    private val notDoubleRegex = Regex("[^\\d]")

    //入力した文字列を変更したことを通知するためのコールバック
    interface Callback {
        fun onNeedTextChange(newText: String)
    }

    //TextWatcherで定義されているメソッド。テキストの内容が変更された時に呼ばれる。
    override fun afterTextChanged(s: Editable?) {
        //入力された数字をカンマ区切りにした文字列を取得し、コールバックとして通知する
        addComma(s?.toString())?.let {
            callback?.onNeedTextChange(it)
        }
    }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }

    //入力された文字列（数字）をカンマ区切りの文字列に変換
    private fun addComma(s: String?): String? = tryFormat(maybeDouble(s))

    //カンマを消去して、Double型に変換
    private fun maybeDouble(s: String?): Double? =
            s?.replace(notDoubleRegex, "")?.toDoubleOrNull()

    //数字をカンマ区切りの文字列に変換
    private fun tryFormat(double: Double?): String? =
            double?.let { numberFormat.format(it) }
}
```

今回は正規表現を使用して、テキストの内容を数字だけの文字列に置換し、Double型に変換させています。その後、NumberFormatのformatメソッドを使用して、カンマ区切りの文字列を取得するようにしています。

カンマ区切りの文字列が取得できたら、コールバックを使って文字列の変更をonNeedTextChangeメソッドで通知しています。

### MainActivity.kt
今回はサンプルなので、EditTextを表示するだけの単純なActivityです。


```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity(), MyTextWatcher.Callback {

    private lateinit var editText: EditText
    private val textWatcher = MyTextWatcher(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        editText = findViewById(R.id.editText)
        editText.addTextChangedListener(textWatcher)
    }

    override fun onNeedTextChange(newText: String) {
        with(editText) {
            /*
            EditTextにリスナーを登録しているままだと、ここで変更する内容に対して、
            TextWatcherのメソッド(afterTextChanged, etc)が再び呼ばれてしまい、無限ループに陥る
            これを回避するために、文字列変更前にリスナーを解除する
            */
            removeTextChangedListener(textWatcher)
            text = SpannableStringBuilder(newText) //カンマ区切りにした数字をEditTextに設定する
            setSelection(text.length) //カーソルの位置を右端に補正する
            /*
            テキストの変更が終わったので、再びテキスト入力の検知ができるようにリスナーを再登録する
             */
            addTextChangedListener(textWatcher)
        }
    }
}
```

onNeedTextChangeメソッドで、カンマ区切りの文字列を受け取りEditTextのテキストを変更しています。
ここで注意が必要なのが、removeTextChangedListenerの呼び出しです。
EditTextにリスナーを登録しているままだと、テキストの内容を変更した際、TextWatcherのメソッド(afterTextChanged, etc)が再び呼ばれてしまい、無限ループに陥ってしまいます。
これを回避するために、文字列変更前にリスナーを解除してあげます。
（リスナー解除以外にも、フラグの使用など方法はあるようですが、今回はリスナー解除の方法を採用）
テキストの変更が終わったら、再びテキスト入力の検知ができるようにaddTextChangedListenerで、再度リスナーを登録してあげましょう。

### activity_main.xml
以下は、EditTextを表示するだけの単純なレイアウトです。
android:inputType="number"を指定し、キーボードからの入力を数値のみに限定します。

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="数値を入力してね"
        android:inputType="number"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
```

TextWatcherを使用すれば、今回のような数字のカンマ区切りだけではなく、テキストの内容を動的に変更するプログラムが書けるようになります。
テキストの内容を自動で変更させたい機能を実装する場合は、ぜひ使ってあげましょう。