TextWatcherは、EditTextやその他のTextViewのテキスト変更をリアルタイムで追跡できるAndroidインターフェースです。開発者は3つの段階で通知を受け取ります:変更前、変更中、変更後です。Android Developers, 2026によると、TextWatcherは入力検証、文字数カウント、オートコンプリート付き検索の実装、動的テキストフォーマットにほとんどのアプリケーションで使用されています。このインターフェースは、キー入力ごとに即時反応が必要なフォームで不可欠です。
重要なポイント
TextWatcherは、android.textパッケージのインターフェースで、Editableオブジェクトのテキスト変更をアプリケーションに通知します。文字の入力、削除、置換のたびに、TextWatcherは3つのメソッドを順番に呼び出し、変更位置に関する情報を渡します。これにより、開発者は追加のボタンやトリガーなしで、ユーザーの操作に即座に反応できます。
主な使用ケースにはリアルタイムフィールド検証が含まれます:各文字入力時のメールアドレスチェック、長さ制限のあるフィールドの残り文字数カウント、デバウンスによる遅延リクエストを用いた検索の実装。TextWatcherは入力フォーマットにも使用されます。例えば、電話番号への自動スペース挿入や日付のマスク追加などです。
Android Developersによると、TextWatcherはフォームを扱うアプリケーションの70%に存在します。Material Design ComponentsやTextInputEditTextなどのライブラリは、エラー状態の管理やカウンター表示のために内部的にTextWatcherを使用しています。このインターフェースの動作を理解することは、すべてのAndroid開発者にとって必須です。
TextWatcherは、addTextChangedListenerメソッドを介して任意のTextViewまたはEditTextオブジェクトに接続します。ユーザーが文字を入力または削除すると、Androidは最初にbeforeTextChanged、次にonTextChanged、最後にafterTextChangedを呼び出します。各メソッドのパラメーターには、変更範囲に関するデータが含まれます:開始位置、削除された文字数、追加された文字数です。
afterTextChangedを呼び出した後、Editableオブジェクトには既に現在の値が含まれていることを理解することが重要です。そのため、afterTextChangedでフィールドの最終テキストを確認するのが便利です。その時点までは、データは完全には更新されていません。開発者はしばしばメソッドの目的を混同し、最終検証にonTextChangedを使用しますが、正しい選択はafterTextChangedです。
各文字の挿入、置換、削除のたびに、呼び出しチェーンは完全に実行されることが保証されています。ただし、afterTextChanged内でテキストが変更された場合(clear、append、insertを使用)、TextWatcherは再帰的に起動します。これはAndroidフォームでのStackOverflowErrorの最も一般的な原因です。再帰を防ぐために、フラグロックが使用されます。
3つのメソッドはそれぞれテキスト変更のライフサイクルにおいて役割を果たします。beforeTextChanged(CharSequence s, int start, int count, int after)メソッドは変更が適用される前に呼び出されます。文字列の現在の状態、変更の開始位置、削除される文字数、追加される文字数を渡します。ここで以前の値を保存したり、変更前に条件をチェックしたりできます。
onTextChangedメソッドは変更中に呼び出され、文字は既に削除されていますが新しい文字はまだ挿入されていません。パラメーター:削除後のテキスト、開始位置、削除された文字数、追加される文字数。このメソッドはアニメーションやログ記録に便利ですが、実際の最終テキストを扱うのには適していません——まだ組み立てられていません。
afterTextChangedメソッドが最も需要があります。Editableオブジェクトを受け取り、変更が完全に適用された後に呼び出されます。このメソッドでは、フィールドの最終値を読み取り、検証を実行し、UIを更新し、テキストを変更できます(再帰に注意)。
実用的な例として、テキスト変更のたびに更新される入力フィールド用の文字カウンターがあります。このような要素は、フィードバックフォーム、投稿、長さ制限のあるメッセージでよく見られます。TextWatcherによる実装は数行で済み、サードパーティのライブラリは必要ありません。
val editText = findViewById<EditText>(R.id.edit_text)
val counterText = findViewById<TextView>(R.id.counter)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence?, start: Int,
count: Int, after: Int
) {}
override fun onTextChanged(
s: CharSequence?, start: Int,
before: Int, count: Int
) {}
override fun afterTextChanged(s: Editable?) {
val len = s?.length ?: 0
counterText.text = "$len / 200"
}
})
例では、afterTextChangedメソッドがEditable型のsパラメーターを介してフィールドの現在の内容を受け取ります。テキストの長さは別のTextViewで更新されます。この場合、counterTextのみが変更され、EditText自体は変更されないため、ループは発生しません。200文字の制限がある場合、超過後に入力を追加でブロックすることもできます。
beforeTextChangedメソッドとonTextChangedメソッドは空のままです。長さのカウントには最終状態で十分だからです。各変更をログ記録する必要がある場合は、onTextChangedにコードを追加できます。この柔軟性により、TextWatcherはテキスト入力のあらゆるシナリオに対応する普遍的なツールとなっています。
リアルタイム検証はUXを大幅に向上させます:ユーザーは送信ボタンをクリックした後ではなく、誤った値を入力した直後にエラーを確認できます。TextWatcherを使用すると、メール、パスワード、電話番号などのフィールドを即座にチェックできます。結果はEditTextのsetError、またはエラーメッセージ付きの別のTextViewを介して表示されます。
fun validateEmail(emailEditText: EditText) {
emailEditText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
val email = s?.toString () ?: ""
if (email.isNotBlank() &&
!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
emailEditText.error = "Invalid email address"
} else {
emailEditText.error = null
}
}
override fun beforeTextChanged(...) {}
override fun onTextChanged(...) {}
})
}
例では、Android SDKの組み込みPatterns.EMAIL_ADDRESSを使用してメールをチェックしています。テキストが空でなくパターンと一致しない場合、errorプロパティを介してフィールドにエラーが設定されます。正しい入力時にはエラーがクリアされます。空のフィールドで検証を実行しないことが重要です——ユーザーがまだ入力を開始していない可能性があり、エラーメッセージは時期尚早になります。
パスワードや電話番号には、カスタム正規表現や専門ライブラリが使用されます。例えば、パスワードの複雑さをチェックするために、数字、大文字、小文字の数をカウントできます。TextWatcherを使用すると、パスワード強度インジケーターをリアルタイムで更新でき、登録コンバージョンに良い影響を与えます。
最初で最も重大な間違いは再帰呼び出しです。afterTextChanged内で同じEditTextのテキストを変更すると(s.clear()、s.append()、s.insert()を使用)、TextWatcherが再び起動します。これにより無限ループが発生し、StackOverflowErrorで終了します。解決策はisUpdatingフラグロックを使用するか、テキストが実際に変更されたかを確認することです。
2番目の一般的な問題はメモリリークです。TextWatcherは匿名クラスを介してActivityやFragmentへの暗黙的な参照を保持します。Viewが破棄される際にリスナーが削除されないと、ガベージコレクターはメモリを解放できません。解決策はライフサイクルコンポーネントを使用するか、onDestroyViewで明示的にremoveTextChangedListenerを呼び出すことです。
3番目の間違いは誤ったメソッドの使用です。一部の開発者はafterTextChangedを待たずにonTextChangedで最終検証を行います。onTextChangedではテキストがまだ完全に更新されておらず、最終値を読み取ると誤ったデータが返される可能性があります。正しいアプローチは、最終テキストの読み取りとチェックのすべてのロジックをafterTextChangedに置くことです。
| メソッド | 呼び出しタイミング | 目的 | 最終テキストを読めるか? |
|---|---|---|---|
| beforeTextChanged | 変更前 | 以前の状態を保存 | はい |
| onTextChanged | 変更中 | ログ記録、アニメーション | いいえ |
| afterTextChanged | 変更後 | 検証、カウント、UI更新 | はい |
4番目の間違いは複数回のTextWatcher追加です。同じEditTextに対してaddTextChangedListenerが複数回呼び出されると、すべてのリスナーが同じ変更を処理します。動的なView追加を伴うフォームでは、これにより重複チェックと予期しない動作が発生します。リスナーが既に追加されているかを常に確認するか、単一のインスタンスを使用してください。
よくある質問
OnTextChangedはテキスト変更の瞬間に呼び出され、新しい文字はまだ追加されていません。このメソッドはアニメーションやログ記録に適しています。AfterTextChangedは変更が完全に適用された後に呼び出され、Editableパラメーターを介して最終テキストへのアクセスを提供します。検証や値の読み取りにはafterTextChangedを使用してください。
Boolean型のフラグロックを使用します。afterTextChanged内でテキストを変更する前にtrueに設定します。メソッドの先頭でフラグをチェックし、trueの場合は終了します。代わりに、古い値と新しい値を比較し、実際に差異がある場合のみテキストを変更することもできます。
はい、必須です。匿名TextWatcherクラスはクロージャーを介してActivityへの参照を保持します。リスナーが削除されないと、Activityはガベージコレクションされません。FragmentのonDestroyViewまたはActivityのonDestroyで必ずremoveTextChangedListenerを呼び出してください。
はい、ただし注意が必要です。RecyclerViewではViewHoldersが再利用され、前の位置のTextWatcherがアクティブなままになる可能性があります。onBindViewHolderメソッドで新しいTextWatcherを設定する前に、必ず古いTextWatcherを削除してください。リスナーへの参照を保存するには、タグやViewHolderの別途フィールドを使用してください。
検索フィールドには、デバウンス(遅延)と組み合わせてafterTextChangedを使用してください。テキスト変更のたびにリセットされる300〜500msのタイマーを実装します。これにより、キー入力ごとにサーバーへリクエストを送信することを防ぎ、APIの負荷を軽減します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。