Gmailの宛先欄みたいなチップ上のテキスト (Chips Edit Text)を実現する

  • 11
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

前置き

chiptext_sample.png
今回開発したサンプルアプリのスクリーンショット

複数の要素をユーザに選ばせるときのインターフェースとして,EditTextに入力された内容で絞り込み,選択された要素をチップとして表示する,というUIが広く使われている(Gmail, Google Hangout, Facebook Messengerなど)

このようなUIは Chips EditText とか Token EditText とか言われているようで,ぐぐってみると使えそうなやつがいくつか出てくる.しかし,Google社員のRoman Nurikが紹介してるやつ はAOSP(Androidオープンソースプロジェクト)の一部として公開されていてなかなかごつい感じだし,kpbird/chipsedittextは自分の用途ではかゆいところに手が届かなかった.

そこで,後者を参考にもうちょっと汎用的な感じで実装しなおした (https://github.com/daisy1754/ChipTextViewHelper )ので,ついでに実装の一部を説明する.後者の作者であるKPBirdさんも実装法の記事(英語)を書いており,本記事はそれの部分的和訳という位置づけに近いので,英語がわかる人は原文読むのが良いかも.

オレオレSpannableを作る

Chipを実現するために,文字列装飾などのためのクラスであるSpannableを利用する.しかし,Spannableは背景画像の設定を許してくれなかったりなど,いろいろ制約がある.デザインはxmlでやりたいけど,きっちりSpannableを継承したクラスを実装するのもだるい.そこで以下のような手順を踏む.

  1. LayoutInflaterによってxmlファイルからViewを生成する
  2. View#layoutで描画結果を取得し,それをコピーしたBitmapを生成する
  3. 2のbitmapをImageSpanとして利用する

具体的には以下のような感じ

class ChipSpan extends ImageSpan {
    public ChipSpan(Context context, int viewResource, int textViewId,
                    int imageViewId, Uri iconUri, CharSequence text) {
        super(context, createBitmap(context, viewResource, textViewId, imageViewId, iconUri, text));
    }

    private static Bitmap createBitmap(Context context, int viewResource, int textViewId,
                                       int imageViewId, Uri iconUri, CharSequence text) {
        LayoutInflater inflater
                = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(viewResource, null);
        ((TextView) view.findViewById(textViewId)).setText(text);
        ((ImageView) view.findViewById(imageViewId)).setImageURI(iconUri);

        int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
        view.measure(spec, spec);

        // Viewの描画サイズを計算する
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());

        // Viewの描画結果を取得するためBitmap/Canvasを用意する
        Bitmap bitmap = Bitmap.createBitmap(
                view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.translate(-view.getScrollX(), -view.getScrollY());
        view.draw(canvas);
        view.setDrawingCacheEnabled(true);

        // 描画結果を新しいBitmapとしてコピー
        Bitmap viewBmp = view.getDrawingCache().copy(Bitmap.Config.ARGB_8888, true);
        view.destroyDrawingCache();
        return viewBmp;
    }
}

SpannableStringBuilderを使ってマークアップつきのテキストを作る

ある文字列をアイコン付きで良い感じに表示する部分が上で可能になったので,あとはどの部分を装飾するかを教えて上げれば良い.SpannableStringBuilderというクラスが使える.

チップとして表示されるべき要素を selectedItems という変数で保持しているとすると

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();

// 文字の指定
for (int i = 0; i < selectedItems.size(); i++) {
    spannableStringBuilder.append(selectedItems.get(i).getDisplayName());
    spannableStringBuilder.append(delimiter);
}

// 装飾の指定
int cursorIndex = 0;
for(ChipItem item : selectedItems){
    String chipText = item.getDisplayName();
    spannableStringBuilder.setSpan(
            new ChipSpan(textView.getContext(), R.layout.default_chip_item,
                    R.id.chip_item_name, R.id.chip_item_icon,
                    item.getIconUri(), chipText),
            cursorIndex, cursorIndex + chipText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    cursorIndex = cursorIndex + chipText.length() + delimiter.length();
}

textView.setText(spannableStringBuilder);

主要な部分はこんな感じ.前置きに書いたように,独立して動くデモアプリのソースコードをGitHubで公開している.主要な部分はライブラリとして切り出してあるので,もし良かったら使ってください.

この投稿は Android Advent Calendar 201311日目の記事です。