EditableとInputFilter

Androidの文字入力に使用するウィジェットであるEditTextクラスでは、文字列の格納のためのプロパティである"Text"に、StringではなくEditableという独自のインタフェースを使用している。

@Override
public Editable getText() {
    return (Editable) super.getText();
}
@Override
public void setText(CharSequence text, BufferType type) {
    super.setText(text, BufferType.EDITABLE);
}

わざわざ別なインタフェースを介しているのは、いろいろな理由があるからだが、そのうちの一つはViewに表示/入力される文字列に対してなんらかの前処理、後処理を施すためだ。
Editableに実装されているInputFilterインタフェース配列のプロパティ"Filters"は、その名の通り、Editableに何らかのフィルタを施すことを可能とする。

public void setFilters(InputFilter filters);
public InputFilter getFilters();

InputFilterインタフェースには只一つのfilterメソッドが宣言されている。

public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend);

使い方は簡単で、用途に合わせたフィルタメソッドを実装したフィルタをEditableにセットするだけだ。同インタフェースには内部クラスとして、既にInputFilterの2つの実装例が公開されており、これを参考にするのが一番手っ取り早い。

  • InputFilterの実装例 ( InputFilter.AllCaps )
public static class AllCaps implements InputFilter {
    public CharSequence filter(CharSequence source, int start, int end,
                               Spanned dest, int dstart, int dend) {
        for (int i = start; i < end; i++) {
            if (Character.isLowerCase(source.charAt(i))) {
                char[] v = new char[end - start];
                TextUtils.getChars(source, start, end, v, 0);
                String s = new String(v).toUpperCase();

                if (source instanceof Spanned) {
                    SpannableString sp = new SpannableString(s);
                    TextUtils.copySpansFrom((Spanned) source,
                                            start, end, null, sp, 0);
                    return sp;
                } else {
                    return s;
                }
            }
        }

        return null; // keep original
    }
}

InputFilter.AllCapsクラスはソースの文字列を全てUpperCase(大文字)に変換するフィルタだ。

ちなみにEditTextのスーパークラスであるTextViewクラスでは、直接内部にフィールドでInputFilter[]を保持し、これをプロパティとして公開しているが、内部ではmTextフィールド(Editable)にフィルタを設定しているので、通常はこちらを使うことになるだろう。

public void setFilters(InputFilter[] filters) {
    if (filters == null) {
        throw new IllegalArgumentException();
    }

    mFilters = filters;

    if (mText instanceof Editable) {
        setFilters((Editable) mText, filters);
    }
}

なお、プロパティとしてはサポートされていないが、レイアウトXMLでのViewの属性"maxlengthを"を設定すると、内部で桁数制限を実装するInputFilter.LengthFilterをインストールする。

if (maxlength >= 0) {
    setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
} else {
    setFilters(NO_FILTERS);
}

この例のようにプロパティでは公開されていないがXML属性ではサポートされている属性が結構あるのも、ちぐはぐな感じが否めない。

どうやらViewのInputFilterは、JFC/SwingのJComponentにおけるDocumentFilterとほぼ同じ感覚で使えそうだ。