Android: TextWatcherによる編集可能なEditText									
		
AndroidでTextWatcherによる編集可能なEditTextの実装方法を整理する。
導入
Androidの開発で,テキストの入力欄のUIウィジェットにEditTextがある。このEditTextはデータの入力UIとして重宝するのだが,これがまた癖がある。
例えば,素の状態であれば入力した内容をそのままデータとして流用できない。別のButtonなどのUIを用意し,そちらの選択時に処理する必要がある。
表示領域の都合などで,処理用のボタンを設置したくない場合がよくある。例えば,EditTextを使ったTextEditorを作る場合なども困る。
TextWatcherを使うことで入力した内容をそのままデータとして活用できる。そこで,TextWatcherによる編集可能なEditTextの実装方法を整理する。方法
EditTextの公式リファレンス (EditText | Android Developers) に,以下のようにEditTextへの入力中の処理の実装方法が書かれている。
You also can receive callbacks as a user changes text by adding aTextWatcherto the edit text. This is useful when you want to add auto-save functionality as changes are made, or validate the format of user input, for example. You add a text watcher using theTextView#addTextChangedListenermethod.
TextWatcherをEditTextに追加することで,ユーザーによるテキストの変更を,コールバックで受け取ることができる。TextView#addTextChangedListenerメソッドの引数に,TextWatcherの実装を追加することで実現する。
TextWatcherはEditTextに入力中の内容を監視するインターフェイスとなっている。テキストの入力時に呼ばれるコールバックは,TextWatcherのリファレンスに記載されており,タイミングに応じて以下の3の抽象メソッドが用意されている。
| メソッド | 説明 | 
|---|---|
abstract void afterTextChanged(Editable s) | テキストの変更後に呼ばれる。 | 
abstract void beforeTextChanged(CharSequence s, int start, int count, int after) | テキストの置換前に呼ばれる。start: 先頭からの位置count: 変更前の選択文字数after: 変更後の選択文字数 | 
abstract void onTextChanged(CharSequence s, int start, int before, int count) | テキストの置換後に呼ばれる。start: 先頭からの位置before: 変更前の選択文字数count: 変更後の選択文字数 | 
beforeTextChangedは入力の反映前に呼ばれる。onTextChangedは入力の反映後に呼ばれる。onTextChangedとafterTextChangedはタイミングが似ている。
テキストの入力直前に何か処理を行いたい場合,beforeTextChangedのタイミングでやる。入力直後のタイミングであれば,onTextChangedとafterTextChangedのどちらでもいい。afterTextChangedの中で文字を追加すると,また
afterTextChangedが呼ばれる。このタイミングを検知する場合に,onTextChangedが必要になる。それ以外は
afterTextChangedでいいだろう。
仮引数に同じ変数名のcountが使われているが,意味が違うことに注意する。
引数の並び順が,入力欄の先頭からの位置 (start),変更前の選択文字数 (count/before),変更後の選択文字数 (after/count) の順番に並んでいると理解すると覚えやすいかもしれない。
例えば,2文字選択して1文字入力する場合count, after, beforeはそれぞれ以下となる。
beforeTextChanged:count=2,after=1onTextChanged:before=2,count=1
入力直後に何か処理を行いたい場合がほとんどなので,基本的にafterTextChangedのみ処理を実装することになるだろう。その場合,beforeTextChangedとonTextChangedの中身は空となる。
implements TextWatcherを記入し,オーバーライドする。サンプル
EditTextの内容をTextViewに表示する簡単なサンプルを以下に掲載する。GitHub上にも公開している。
なお,Android 3.5.3で確認した。
package jp.senooken.android.edittextwithtextwatcher;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EditText et = findViewById(R.id.editText);
        et.addTextChangedListener(new MyTextWatcher());
    }
    private class MyTextWatcher implements TextWatcher {
        private final TextView afterTextChanged_ = findViewById(R.id.afterTextChanged);
        private final TextView beforeTextChanged_ = findViewById(R.id.beforeTextChanged);
        private final TextView onTextChanged_ = findViewById(R.id.onTextChanged);
        @Override
        public void afterTextChanged(Editable s) {
            String input= s.toString();
            afterTextChanged_.setText(input);
        }
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            String input= "start=" + start
                    + ", count=" + count
                    + ", after=" + after
                    + ", s=" + s.toString();
            beforeTextChanged_.setText(input);
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            String input= "start=" + start
                    + ", before=" + before
                    + ", count=" + count
                    + ", s=" + s.toString();
            onTextChanged_.setText(input);
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/afterTextChanged" />
        <TextView
            android:id="@+id/afterTextChanged"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/textView4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/beforeTextChanged" />
        <TextView
            android:id="@+id/beforeTextChanged"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/textView5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/onTextChanged" />
        <TextView
            android:id="@+id/onTextChanged"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:autofillHints="number"
        android:ems="10"
        android:inputType="number"
        android:hint="@string/hint"
        tools:targetApi="o" />
</LinearLayout>
<resources>
    <string name="app_name">EditTextWithTextWatcher</string>
    <string name="hint">Fill this.</string>
    <string name="afterTextChanged">afterTextChanged: </string>
    <string name="beforeTextChanged">beforeTextChanged: </string>
    <string name="onTextChanged">onTextChanged: </string>
</resources>
EditTextに入力した内容を3の抽象メソッドに対応したTextViewに引数と共に表示させている。
これで,beforeTextChangedとonTextChangedの引数の意味について理解できる。
結論
EditTextに入力された内容をTextWatcherで処理する方法を整理した。
EditTextを扱う上でほぼ必須の処理なので,整理できてよかった。地味に,公式リファレンスのbeforeTextChangedとonTextChangedの引数の意味がわかりにくくて,これの意味を理解するのに時間がかかった。
やはり自分で簡単な動作するサンプルを作成してみるのが,手間はかかるものの効果的だと感じた。
今後も,サンプルを作りながら理解を深めていきたい。

“Android:
TextWatcherによる編集可能なEditText” に対して1件のコメントがあります。