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

### 前置き
![chiptext_sample.png](https://qiita-image-store.s3.amazonaws.com/0/6000/6fae8df1-50c6-3436-caf3-fdef47082d87.png)
今回開発した[サンプルアプリ](https://github.com/daisy1754/ChipTextViewHelper/tree/master/demo)のスクリーンショット

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

このようなUIは _Chips EditText_ とか _Token EditText_ とか言われているようで，ぐぐってみると使えそうなやつがいくつか出てくる．しかし，[Google社員のRoman Nurikが紹介してるやつ](https://plus.google.com/+RomanNurik/posts/WUd7GrfZfiZ) はAOSP(Androidオープンソースプロジェクト)の一部として公開されていてなかなかごつい感じだし，[kpbird/chipsedittext](https://github.com/kpbird/chipsedittext)は自分の用途ではかゆいところに手が届かなかった．

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


## オレオレSpannableを作る
Chipを実現するために，文字列装飾などのためのクラスである[Spannable](http://developer.android.com/reference/android/text/Spannable.html)を利用する．しかし，Spannableは背景画像の設定を許してくれなかったりなど，いろいろ制約がある．デザインはxmlでやりたいけど，きっちりSpannableを継承したクラスを実装するのもだるい．そこで以下のような手順を踏む．

1. [LayoutInflater](http://developer.android.com/reference/android/view/LayoutInflater.html)によってxmlファイルからViewを生成する
2. View#layoutで描画結果を取得し，それをコピーしたBitmapを生成する
3. 2のbitmapを[ImageSpan](http://developer.android.com/reference/android/text/style/ImageSpan.html)として利用する

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

```java
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](http://developer.android.com/reference/android/text/SpannableStringBuilder.html)というクラスが使える．

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

```java
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);
```

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