AutoCompleteAdapter

AutoCompleteTextViewで使用するためのAdapter。
AutoCompleteTextViewは、単純に選択肢を表示するだけならArrayAdapterを使うと楽だが、ビューをカスタマイズしようとすると途端にややこしくなる。
自分で作ってていろいろハマッたので、そこらへんをラップしたクラスを作った。
基本、3つのメソッドをオーバーライドするだけで使えるはず。


あまり意味は無いが、ArrayAdapterと同じような動作をさせるコードはこんな感じ。

public class SampleActivity extends Activity {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // mainはAutoCompleteTextViewが1つあるだけのレイアウト
    setContentView(R.layout.main);
    AutoCompleteTextView t = (AutoCompleteTextView) findViewById(R.id.AutoCompleteTextView01);
    // keywordsは適当な文字列の配列リソース
    t.setAdapter(new AutoCompleteAdapter<String>(Arrays.asList(getResources().getStringArray(R.array.keywords))) {
      @Override
      protected View createView(int i) {
        return LayoutInflater.from(SampleActivity.this).inflate(android.R.layout.simple_list_item_1, null);
      }
      @Override
      protected boolean isMatch(CharSequence input, String item) {
        return item.startsWith(input.toString());
      }
      @Override
      protected void refreshView(int i, View view) {
        ((TextView)view).setText((CharSequence) getItem(i));
      }
    });
  }
}
/**
 * AutoCompleteTextView用のAdapter
 *
 * @param <V> リストに含まれるアイテムの型
 */
public abstract class AutoCompleteAdapter<V> extends BaseAdapter implements Filterable {

  private List<V> allItems;
  private List<V> filterdItems;
  private boolean alwaysCreateView;

  /**
   * すべてのアイテムで共通のビューを使う場合のコンストラクタ
   * @param items すべての選択肢のリスト
   */
  public AutoCompleteAdapter(List<V> items) {
    this(items, false);
  }

  /**
   * ビューの生成タイミングを指定する場合のコンストラクタ
   * @param items すべての選択肢のリスト
   * @param alwaysCreateView {@link #createView(int)}参照
   */
  public AutoCompleteAdapter(List<V> items, boolean alwaysCreateView) {
    this.allItems = items;
    this.alwaysCreateView = alwaysCreateView;
  }

  /**
   * アイテムの表示に使用するビューを作成する。<br/>
   * コンストラクタのalwaysCreateView引数がtrueであれば、アイテムを表示するたびに呼ばれる。<br/>
   * コンストラクタのalwaysCreateView引数がfalseであれば、必要に応じて呼ばれる。(一つのビューが複数のアイテムで使いまわされることがある)
   * @param i 表示するアイテムのインデックス
   * @return
   */
  protected abstract View createView(int i);

  /**
   * ビューの内容を更新する
   * @param i 表示するアイテムのインデックス
   * @param view 表示に使用するビュー
   */
  protected abstract void refreshView(int i, View view);

  /**
   * アイテムを選択肢に表示するか否かを返す
   * @param input AutoCompleteTextViewに入力された文字列
   * @param item 比較対象のアイテム
   * @return trueであればitemが選択肢に表示される
   */
  protected abstract boolean isMatch(CharSequence input, V item);

  /**
   * @see ListAdapter#getCount()
   */
  public int getCount() {
    return filterdItems.size();
  }

  /**
   * @see ListAdapter#getItem(int)
   */
  public Object getItem(int i) {
    return filterdItems.get(i);
  }

  /**
   * @see ListAdapter#getItemId(int)
   */
  public long getItemId(int i) {
    return i;
  }

  /**
   * @see ListAdapter#getView(int, View, ViewGroup)
   */
  public View getView(int i, View view, ViewGroup viewgroup) {
    // ビューが作成されていないか、アイテムごとに異なるビューを使用する場合は新たにビューを作成する
    if (view == null || alwaysCreateView) {
      view = createView(i);
    }
    // アイテムの内容をビューに反映する
    refreshView(i, view);
    return view;
  }

  /**
   * @see Filterable#getFilter()
   */
  public Filter getFilter() {
    return new Filter() {

      @Override
      protected FilterResults performFiltering(CharSequence charsequence) {
        FilterResults ret = new FilterResults();
        List<V> list = new ArrayList<V>();
        if (charsequence == null) {
          // 文字が入力されていなければすべての選択肢を表示する
          // 実質不要?
          list.addAll(allItems);
        } else {
          // アイテムごとに選択肢に表示するか否かを判定する
          for (V item : allItems) {
            if (isMatch(charsequence, item)) {
              list.add(item);
            }
          }
        }
        ret.values = list;
        ret.count = list.size();
        return ret;
      }

      @SuppressWarnings("unchecked")
      @Override
      protected void publishResults(CharSequence charsequence,
          FilterResults filterresults) {
        filterdItems = (List<V>) filterresults.values;
        if (filterresults != null && filterresults.count > 0) {
          notifyDataSetChanged();
        } else {
          notifyDataSetInvalidated();
        }
      }

    };
  }
}