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 a TextWatcher to 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 the TextView#addTextChangedListener method.
EditText | Android Developers

TextWatcherEditTextに追加することで,ユーザーによるテキストの変更を,コールバックで受け取ることができる。TextView#addTextChangedListenerメソッドの引数に,TextWatcherの実装を追加することで実現する。

TextWatcherEditTextに入力中の内容を監視するインターフェイスとなっている。テキストの入力時に呼ばれるコールバックは,TextWatcherのリファレンスに記載されており,タイミングに応じて以下の3の抽象メソッドが用意されている。

TextWatcherの抽象メソッド
メソッド説明
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のタイミングでやる。入力直後のタイミングであれば,onTextChangedafterTextChangedのどちらでもいい。afterTextChangedの中で文字を追加すると,また afterTextChangedが呼ばれる。このタイミングを検知する場合に,onTextChangedが必要になる。それ以外は afterTextChangedでいいだろう。

仮引数に同じ変数名のcountが使われているが,意味が違うことに注意する。

引数の並び順が,入力欄の先頭からの位置 (start),変更前の選択文字数 (count/before),変更後の選択文字数 (after/count) の順番に並んでいると理解すると覚えやすいかもしれない。

例えば,2文字選択して1文字入力する場合count, after, beforeはそれぞれ以下となる。

  • beforeTextChanged: count=2, after=1
  • onTextChanged: before=2, count=1

入力直後に何か処理を行いたい場合がほとんどなので,基本的にafterTextChangedのみ処理を実装することになるだろう。その場合,beforeTextChangedonTextChangedの中身は空となる。

実際に実装する場合,実装先クラスにimplements TextWatcherを記入し,オーバーライドする。
サンプル

EditTextの内容をTextViewに表示する簡単なサンプルを以下に掲載する。GitHub上にも公開している。

なお,Android 3.5.3で確認した。

MainActivity.java
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);
        }
    }
}
activity_main.xml
<?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>
strings.xml
<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に引数と共に表示させている。

これで,beforeTextChangedonTextChangedの引数の意味について理解できる。

結論

EditTextに入力された内容をTextWatcherで処理する方法を整理した。

EditTextを扱う上でほぼ必須の処理なので,整理できてよかった。地味に,公式リファレンスのbeforeTextChangedonTextChangedの引数の意味がわかりにくくて,これの意味を理解するのに時間がかかった。

やはり自分で簡単な動作するサンプルを作成してみるのが,手間はかかるものの効果的だと感じた。

今後も,サンプルを作りながら理解を深めていきたい。

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

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です