前書き
何が問題になっているのかは質問からは明確ではないので、この機能を実装する方法について簡単に説明しました。それでも質問がある場合は、遠慮なく質問してください。
このGitHubリポジトリには、ここで話しているすべての実例があります。
サンプルプロジェクトについて詳しく知りたい場合は、プロジェクトのホームページにアクセスしてください。
いずれにしても、結果は次のようになります。

最初にデモアプリをいじりたい場合は、Playストアからインストールできます。

とにかく始めましょう。
のセットアップ SearchView
フォルダ内にres/menuという新しいファイルを作成しますmain_menu.xml。その中にアイテムを追加し、をに設定actionViewClassしandroid.support.v7.widget.SearchViewます。サポートライブラリを使用しているので、サポートライブラリの名前空間を使用してactionViewClass属性を設定する必要があります。xmlファイルは次のようになります。
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_search"
android:title="@string/action_search"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always"/>
</menu>
あなたにFragmentまたはActivityあなたがいつものように、このメニューXMLを膨らませる必要があり、その後、あなたはを探すことができますMenuItem含まれているSearchViewと実装OnQueryTextListener我々が締結したテキストへの変更をリッスンするために使用しようとしていますSearchView。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
final MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setOnQueryTextListener(this);
return true;
}
@Override
public boolean onQueryTextChange(String query) {
// Here is where we are going to implement the filter logic
return false;
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
これで、SearchView使用する準備が整いました。の実装onQueryTextChange()が完了したら、後でフィルターロジックを実装しAdapterます。
のセットアップ Adapter
何よりもまず、これはこの例で使用するモデルクラスです。
public class ExampleModel {
private final long mId;
private final String mText;
public ExampleModel(long id, String text) {
mId = id;
mText = text;
}
public long getId() {
return mId;
}
public String getText() {
return mText;
}
}
にテキストを表示するのは、単なる基本モデルRecyclerViewです。これは、テキストを表示するために使用するレイアウトです。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="com.github.wrdlbrnft.searchablerecyclerviewdemo.ui.models.ExampleModel"/>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@{model.text}"/>
</FrameLayout>
</layout>
ご覧のとおり、データバインディングを使用しています。これまでにデータバインディングを使用したことがない場合は、落胆しないでください。それは非常にシンプルで強力ですが、この回答の範囲内でどのように機能するかは説明できません。
これがあるViewHolderためExampleModel、クラス:
public class ExampleViewHolder extends RecyclerView.ViewHolder {
private final ItemExampleBinding mBinding;
public ExampleViewHolder(ItemExampleBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
public void bind(ExampleModel item) {
mBinding.setModel(item);
}
}
ここでも特別なことは何もありません。上記のレイアウトxmlで定義したように、データバインディングを使用してモデルクラスをこのレイアウトにバインドするだけです。
これで、ようやく本当に興味深い部分であるアダプターの作成にたどり着くことができます。私はの基本的な実装をスキップして、Adapter代わりにこの回答に関連する部分に集中します。
しかし、最初に私たちが話し合わなければならないことが1つありSortedListます。それはクラスです。
SortedList
SortedList一部であり、完全に素晴らしいツールであるRecyclerViewライブラリ。これはAdapter、データセットの変更に関する通知を処理し、非常に効率的な方法で通知します。必要なのは、要素の順序を指定することだけです。のようにcompare()2つの要素を比較するメソッドを実装することにより、これを行う必要があります。ただし、aをソートする代わりに、!内のアイテムをソートするために使用されます。SortedListComparatorListRecyclerView
SortedList相互作用Adapterを通じてCallback、あなたが実装する必要がクラス:
private final SortedList.Callback<ExampleModel> mCallback = new SortedList.Callback<ExampleModel>() {
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
mAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
mAdapter.notifyItemRangeChanged(position, count);
}
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
}
以下のようなコールバックの上部の方法ではonMoved、onInsertedなど、あなたの方法を通知同等のものを呼び出す必要がありますAdapter。3つの下部の方法compare、areContentsTheSameそしてareItemsTheSameあなたが表示し、これらのオブジェクトは、画面に表示されます注文何にするオブジェクトの種類に応じて実装する必要があります。
これらの方法を1つずつ見ていきましょう。
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
これは、compare()先ほどお話しした方法です。この例ではComparator、2つのモデルを比較するaに呼び出しを渡しています。画面にアイテムをアルファベット順に表示する場合。このコンパレータは次のようになります。
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
次のメソッドを見てみましょう:
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
このメソッドの目的は、モデルのコンテンツが変更されたかどうかを判断することです。SortedList他の言葉であれば-変更イベントが呼び出される必要があるかどうかを判断するために、これを使用してRecyclerView古いものと新しいバージョンをクロスフェードする必要があります。モデルクラスが正しく実装されている場合equals()、hashCode()通常は上記のように実装できます。クラスにequals()とhashCode()実装を追加すると、ExampleModel次のようになります。
public class ExampleModel implements SortedListAdapter.ViewModel {
private final long mId;
private final String mText;
public ExampleModel(long id, String text) {
mId = id;
mText = text;
}
public long getId() {
return mId;
}
public String getText() {
return mText;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExampleModel model = (ExampleModel) o;
if (mId != model.mId) return false;
return mText != null ? mText.equals(model.mText) : model.mText == null;
}
@Override
public int hashCode() {
int result = (int) (mId ^ (mId >>> 32));
result = 31 * result + (mText != null ? mText.hashCode() : 0);
return result;
}
}
簡単な補足:Android Studio、IntelliJ、EclipseなどのほとんどのIDEには、ボタンを押すだけで生成equals()およびhashCode()実装する機能があります。したがって、自分で実装する必要はありません。IDEでの動作をインターネットで調べてください。
次に、最後のメソッドを見てみましょう。
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
SortedList2つの項目が同じものを指しているかどうかを確認するために、このメソッドを使用しています。簡単に言えば(SortedList動作の説明はありません)、これは、オブジェクトが既にに含まれてListいるかどうか、および追加、移動、または変更のアニメーションを再生する必要があるかどうかを判断するために使用されます。モデルにIDがある場合は、通常、このメソッドでIDのみを比較します。そうでない場合は、他の方法でこれを確認する必要がありますが、実装は最終的には特定のアプリによって異なります。通常、すべてのモデルにIDを与えるのが最も簡単なオプションです。たとえば、データベースからデータをクエリする場合、これは主キーフィールドになる可能性があります。
ではSortedList.Callback正しく実装我々はのインスタンスを作成することができますSortedList。
final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);
のコンストラクタの最初のパラメータとして、SortedListモデルのクラスを渡す必要があります。もう1つのパラメーターは、上SortedList.Callbackで定義したとおりです。
さて、ビジネスに取り掛かりましょう:Adapterwith を実装すると、SortedList次のようになります。
public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {
private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
});
private final LayoutInflater mInflater;
private final Comparator<ExampleModel> mComparator;
public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
mInflater = LayoutInflater.from(context);
mComparator = comparator;
}
@Override
public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
return new ExampleViewHolder(binding);
}
@Override
public void onBindViewHolder(ExampleViewHolder holder, int position) {
final ExampleModel model = mSortedList.get(position);
holder.bind(model);
}
@Override
public int getItemCount() {
return mSortedList.size();
}
}
Comparator私たちは同じを使用できるようにアイテムをソートするために使用さは、コンストラクタ経由で渡されるAdapter項目が異なる順序で表示されるようになっている場合でも。
これでほぼ完了です。ただし、最初にに項目を追加または削除する方法が必要Adapterです。この目的のために、にメソッドAdapterを追加して、アイテムをに追加および削除できるようにすることができますSortedList。
public void add(ExampleModel model) {
mSortedList.add(model);
}
public void remove(ExampleModel model) {
mSortedList.remove(model);
}
public void add(List<ExampleModel> models) {
mSortedList.addAll(models);
}
public void remove(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (ExampleModel model : models) {
mSortedList.remove(model);
}
mSortedList.endBatchedUpdates();
}
我々はので、ここで任意の通知メソッドを呼び出す必要はありませんSortedListすでに通過のためにこれを行いますSortedList.Callback!それを除けば、これらのメソッドの実装は、1つの例外を除いて非常に単純ですList。モデルのaを削除するremoveメソッドです。にSortedListは、単一のオブジェクトを削除できるremoveメソッドが1つしかないため、リストをループしてモデルを1つずつ削除する必要があります。beginBatchedUpdates()最初に呼び出すと、SortedList一緒に行うすべての変更がバッチ処理され、パフォーマンスが向上します。を呼び出すとendBatchedUpdates()、RecyclerViewすべての変更が一度に通知されます。
さらに、理解する必要があるのは、オブジェクトをに追加し、SortedListそれがすでにSortedListそこにある場合、再度追加されないことです。代わりに、SortedListはareContentsTheSame()メソッドを使用して、オブジェクトが変更されたかどうか、およびオブジェクトがにあるかどうかをRecyclerView更新します。
とにかく、私が通常好むのはRecyclerView、一度にすべてのアイテムを交換できる1つの方法です。にないものをすべて削除し、List不足しているすべての項目をに追加しますSortedList。
public void replaceAll(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (int i = mSortedList.size() - 1; i >= 0; i--) {
final ExampleModel model = mSortedList.get(i);
if (!models.contains(model)) {
mSortedList.remove(model);
}
}
mSortedList.addAll(models);
mSortedList.endBatchedUpdates();
}
この方法でも、すべての更新をバッチ処理してパフォーマンスを向上させます。最初のループは逆です。最初にアイテムを削除すると、その後に表示されるすべてのアイテムのインデックスが混乱し、場合によってはデータの不整合などの問題が発生する可能性があるためです。後私達はちょうど追加することListにSortedList使用addAll()してまだ入っていないすべての項目を追加するSortedListと-私は上記と同じように-更新に既にあるすべての項目SortedListが、変更されています。
これAdapterで完了です。全体は次のようになります。
public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {
private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1 == item2;
}
});
private final Comparator<ExampleModel> mComparator;
private final LayoutInflater mInflater;
public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
mInflater = LayoutInflater.from(context);
mComparator = comparator;
}
@Override
public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(mInflater, parent, false);
return new ExampleViewHolder(binding);
}
@Override
public void onBindViewHolder(ExampleViewHolder holder, int position) {
final ExampleModel model = mSortedList.get(position);
holder.bind(model);
}
public void add(ExampleModel model) {
mSortedList.add(model);
}
public void remove(ExampleModel model) {
mSortedList.remove(model);
}
public void add(List<ExampleModel> models) {
mSortedList.addAll(models);
}
public void remove(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (ExampleModel model : models) {
mSortedList.remove(model);
}
mSortedList.endBatchedUpdates();
}
public void replaceAll(List<ExampleModel> models) {
mSortedList.beginBatchedUpdates();
for (int i = mSortedList.size() - 1; i >= 0; i--) {
final ExampleModel model = mSortedList.get(i);
if (!models.contains(model)) {
mSortedList.remove(model);
}
}
mSortedList.addAll(models);
mSortedList.endBatchedUpdates();
}
@Override
public int getItemCount() {
return mSortedList.size();
}
}
今欠けている唯一のものはフィルタリングを実装することです!
フィルターロジックの実装
フィルターロジックを実装するには、まずListすべての可能なモデルを定義する必要があります。この例では、私は作成ListのExampleModel映画の配列からインスタンスを:
private static final String[] MOVIES = new String[]{
...
};
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
private ExampleAdapter mAdapter;
private List<ExampleModel> mModels;
private RecyclerView mRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mAdapter = new ExampleAdapter(this, ALPHABETICAL_COMPARATOR);
mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
mBinding.recyclerView.setAdapter(mAdapter);
mModels = new ArrayList<>();
for (String movie : MOVIES) {
mModels.add(new ExampleModel(movie));
}
mAdapter.add(mModels);
}
ここでは特別なことは何もせず、をインスタンス化してAdapterに設定しRecyclerViewます。その後List、MOVIES配列の映画名からモデルのを作成します。次に、すべてのモデルをに追加しますSortedList。
これで、onQueryTextChange()前に定義したものに戻り、フィルターロジックの実装を開始できます。
@Override
public boolean onQueryTextChange(String query) {
final List<ExampleModel> filteredModelList = filter(mModels, query);
mAdapter.replaceAll(filteredModelList);
mBinding.recyclerView.scrollToPosition(0);
return true;
}
これもまた非常に簡単です。メソッドを呼び出し、ofとクエリ文字列filter()を渡します。次に、を呼び出して、から返されたフィルター済みのフィルターを渡します。また、呼び出す必要がありますに何かを検索する際に、ユーザは常にすべての項目を見ることができることを保証します。そうしないと、はフィルタリング中にスクロールダウン位置にとどまり、その後いくつかの項目を非表示にする可能性があります。上にスクロールすると、検索中のユーザーエクスペリエンスが向上します。ListExampleModelreplaceAll()AdapterListfilter()scrollToPosition(0)RecyclerViewRecyclerView
あとは、filter()自分自身を実装するだけです。
private static List<ExampleModel> filter(List<ExampleModel> models, String query) {
final String lowerCaseQuery = query.toLowerCase();
final List<ExampleModel> filteredModelList = new ArrayList<>();
for (ExampleModel model : models) {
final String text = model.getText().toLowerCase();
if (text.contains(lowerCaseQuery)) {
filteredModelList.add(model);
}
}
return filteredModelList;
}
ここで最初にすることはtoLowerCase()、クエリ文字列の呼び出しです。検索機能で大文字と小文字を区別したくないので、toLowerCase()比較するすべての文字列を呼び出すことで、大文字と小文字を区別せずに同じ結果を返すことができます。次に、List渡されたのすべてのモデルを反復処理し、クエリ文字列がモデルのテキストに含まれているかどうかを確認します。そうである場合、モデルはフィルターに追加されListます。
以上です!上記のコードはAPIレベル7以上で実行され、APIレベル11以降、アイテムのアニメーションを無料で入手できます!
これは非常に詳細な説明であるため、おそらくこの全体が実際よりも複雑に見えますが、この全体の問題を一般化して、にAdapter基づいSortedListてをより簡単に実装できるようにする方法があります。
問題の一般化とアダプターの簡素化
このセクションでは、詳細には触れません。スタックオーバーフローでの回答の文字数制限に達しているためですが、そのほとんどがすでに上記で説明されているためですが、変更を要約すると、次のようになります。基本Adapterクラスを実装できます。これは、SortedListモデルをViewHolderインスタンスにバインドするだけでなく、の処理もすでに処理しており、にAdapter基づいてを実装する便利な方法を提供しますSortedList。そのためには、2つのことを行う必要があります。
ViewModelすべてのモデルクラスが実装しなければならないインターフェースを作成する必要があります
- モデルを自動的にバインドするために使用できるメソッド
ViewHolderを定義するサブクラスを作成する必要があります。bind()Adapter
これによりRecyclerView、モデルとそれに対応するViewHolder実装を実装するだけで、に表示されることになっているコンテンツに焦点を合わせることができます。この基本クラスを使用するAdapterと、とそのの複雑な詳細について心配する必要はありませんSortedList。
SortedListAdapter
このため、基本クラスを実装するか、ここでも完全なソースコードを追加していますが、この基本クラスの完全なソースコードを見つけることができるの各ステップを経ることができないStackOverflowの私の答えの文字数制限の-私はそれを呼ばれるSortedListAdapter-これでGitHubの要旨。
あなたの人生をシンプルにするために、SortedListAdapter!を含むライブラリをjCenterに公開しました。使用したい場合は、この依存関係をアプリのbuild.gradleファイルに追加するだけです。
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
このライブラリの詳細については、ライブラリホームページをご覧ください。
SortedListAdapterの使用
を使用するSortedListAdapterには、2つの変更を行う必要があります。
ViewHolder拡張するように変更しますSortedListAdapter.ViewHolder。typeパラメータは、これにバインドされるモデルでなければなりませんViewHolder-この場合はExampleModel。performBind()ではなく、モデルにデータをバインドする必要がありますbind()。
public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> {
private final ItemExampleBinding mBinding;
public ExampleViewHolder(ItemExampleBinding binding) {
super(binding.getRoot());
mBinding = binding;
}
@Override
protected void performBind(ExampleModel item) {
mBinding.setModel(item);
}
}
すべてのモデルがViewModelインターフェースを実装していることを確認してください:
public class ExampleModel implements SortedListAdapter.ViewModel {
...
}
その後は、を更新して、不要になったすべてをExampleAdapter拡張SortedListAdapterして削除する必要があります。typeパラメータは、使用しているモデルのタイプである必要があります-この場合はExampleModel。ただし、さまざまなタイプのモデルを使用している場合は、typeパラメータをに設定しますViewModel。
public class ExampleAdapter extends SortedListAdapter<ExampleModel> {
public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
super(context, ExampleModel.class, comparator);
}
@Override
protected ViewHolder<? extends ExampleModel> onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) {
final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
return new ExampleViewHolder(binding);
}
@Override
protected boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
@Override
protected boolean areItemContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
}
その後、完了です!言及するしかし最後の一つです:SortedListAdapter同じ持っていないadd()、remove()またはreplaceAll()独自の方法ExampleAdapterでしたが。別のEditorオブジェクトを使用して、edit()メソッドを介してアクセスできるリストの項目を変更します。したがって、呼び出す必要がある項目を削除または追加する場合はedit()、このEditorインスタンスで項目を追加および削除し、完了したら、呼び出しcommit()て変更をに適用しますSortedList。
mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();
この方法で行うすべての変更は、パフォーマンスを向上させるためにバッチ処理されます。replaceAll()我々は上記の章で実装される方法はまた、この上に存在するEditorオブジェクト:
mAdapter.edit()
.replaceAll(mModels)
.commit();
呼び出すのを忘れた場合commit()、変更は適用されません。