サポートライブラリからSearchView
を実装しようとしています。私は、ユーザーがSearchView
内の映画のList
をフィルタリングするためにRecyclerView
を使用することを望んでいます。
私はこれまでにいくつかのチュートリアルをたどり、SearchView
をActionBar
に追加しましたが、ここからどこへ行くべきかはよくわかりません。いくつか例を見てきましたが、入力しても結果が表示されません。
これは私のMainActivity
です:
public class MainActivity extends ActionBarActivity {
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
RecyclerView.Adapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recycler_view);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new CardAdapter() {
@Override
public Filter getFilter() {
return null;
}
};
mRecyclerView.setAdapter(mAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
そしてこれが私のAdapter
です:
public abstract class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> implements Filterable {
List<Movie> mItems;
public CardAdapter() {
super();
mItems = new ArrayList<Movie>();
Movie movie = new Movie();
movie.setName("Spiderman");
movie.setRating("92");
mItems.add(movie);
movie = new Movie();
movie.setName("Doom 3");
movie.setRating("91");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers");
movie.setRating("88");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 2");
movie.setRating("87");
mItems.add(movie);
movie = new Movie();
movie.setName("Transformers 3");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Noah");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 2");
movie.setRating("86");
mItems.add(movie);
movie = new Movie();
movie.setName("Ironman 3");
movie.setRating("86");
mItems.add(movie);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
Movie movie = mItems.get(i);
viewHolder.tvMovie.setText(movie.getName());
viewHolder.tvMovieRating.setText(movie.getRating());
}
@Override
public int getItemCount() {
return mItems.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
public TextView tvMovie;
public TextView tvMovieRating;
public ViewHolder(View itemView) {
super(itemView);
tvMovie = (TextView)itemView.findViewById(R.id.movieName);
tvMovieRating = (TextView)itemView.findViewById(R.id.movieRating);
}
}
}
あなたの質問からあなたが何に問題を抱えているのかが本当に明確ではないので、この機能を実装する方法についてのこのクイックウォークスルーを書きました。まだ質問がある場合はお気軽にお問い合わせください。
この 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
を使用する準備が整いました。 Adapter
の実装が完了したら、後でonQueryTextChange()
でフィルターロジックを実装します。
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
は、RecyclerView
ライブラリーの一部である完全に素晴らしいツールです。データセットの変更についてAdapter
に通知し、非常に効率的な方法で通知します。必要なことは、要素の順序を指定することだけです。 SortedList
のようにComparator
の2つの要素を比較するcompare()
メソッドを実装することにより、それを行う必要があります。ただし、List
をソートする代わりに、RecyclerView
!内のアイテムをソートするために使用されます。
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
の同等の通知メソッドを呼び出す必要があります。一番下のcompare
、areContentsTheSame
、およびareItemsTheSame
の3つのメソッドは、表示するオブジェクトの種類と、これらのオブジェクトが画面に表示される順序に従って実装する必要があります。
これらのメソッドを1つずつ見ていきましょう。
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
これは、前に説明したcompare()
メソッドです。この例では、2つのモデルを比較するComparator
に呼び出しを渡すだけです。画面にアイテムをアルファベット順に表示する場合。このコンパレータは次のようになります。
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()
実装がある場合、通常は上記のように実装できます。 ExampleModel
クラスにequals()
およびhashCode()
実装を追加すると、次のようになります。
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();
}
SortedList
はこのメソッドを使用して、2つのアイテムが同じものを参照しているかどうかを確認します。最も簡単な用語(SortedList
の動作方法は説明しません)を使用して、オブジェクトが既にList
に含まれているかどうか、およびアニメーションを追加、移動、または変更する必要があるかどうかを判断します。モデルにIDがある場合、通常はこのメソッドのIDのみを比較します。そうでない場合、これをチェックする他の方法を理解する必要がありますが、これを実装することになるのは特定のアプリに依存します。通常、すべてのモデルにIDを与えるのが最も簡単なオプションです。たとえば、データベースからデータをクエリする場合、主キーフィールドになります。
SortedList.Callback
を正しく実装すると、SortedList
のインスタンスを作成できます。
final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);
SortedList
のコンストラクターの最初のパラメーターとして、モデルのクラスを渡す必要があります。他のパラメーターは、上記で定義したSortedList.Callback
のみです。
では、ビジネスに取りかかりましょう。Adapter
を使用して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
を削除する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();
}
このメソッドは、すべての更新をまとめてバッチ処理して、パフォーマンスを向上させます。最初のループは逆になります。開始時にアイテムを削除すると、その後に出現するすべてのアイテムのインデックスが台無しになり、データの不整合などの問題が発生する可能性があるためです。その後、_addAll()
を使用してList
にSortedList
を追加し、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;
}
これも非常に簡単です。メソッドfilter()
を呼び出し、List
sのExampleModel
とクエリ文字列を渡します。次に、Adapter
でreplaceAll()
を呼び出し、filter()
によって返されたフィルター処理されたList
を渡します。また、RecyclerView
でscrollToPosition(0)
を呼び出して、ユーザーが何かを検索するときに常にすべてのアイテムを表示できるようにする必要があります。そうしないと、RecyclerView
がフィルタリング中にスクロールダウン位置のままになり、その後いくつかのアイテムが非表示になる場合があります。一番上までスクロールすると、検索中のユーザーエクスペリエンスが向上します。
あとは、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
の複雑な詳細について心配する必要はありません。
StackOverflowの回答には文字数制限があるため、この基本クラスを実装する各ステップを実行することも、ここに完全なソースコードを追加することもできませんが、この基本クラスの完全なソースコードを見つけることができます-SortedListAdapter
-in this GitHub Gist 。
あなたの人生を簡単にするために、SortedListAdapter
を含むライブラリをjCenterに公開しました!使用したい場合は、この依存関係をアプリのbuild.gradleファイルに追加するだけです。
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
このライブラリに関する詳細情報を見つけることができます ライブラリのホームページで 。
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
には、元のExampleAdapter
にあったadd()
、remove()
、またはreplaceAll()
メソッドがありません。別のEditor
オブジェクトを使用して、edit()
メソッドを介してアクセスできるリスト内の項目を変更します。したがって、edit()
を呼び出す必要があるアイテムを削除または追加する場合は、このEditor
インスタンスのアイテムを追加および削除し、完了したら、commit()
を呼び出して、SortedList
に変更を適用します。
mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();
この方法で行うすべての変更は、パフォーマンスを向上させるためにまとめられます。上記の章で実装したreplaceAll()
メソッドは、このEditor
オブジェクトにも存在します。
mAdapter.edit()
.replaceAll(mModels)
.commit();
commit()
の呼び出しを忘れると、変更は適用されません!
RecyclerView.Adapter
にfilter
メソッドを追加するだけです。
public void filter(String text) {
items.clear();
if(text.isEmpty()){
items.addAll(itemsCopy);
} else{
text = text.toLowerCase();
for(PhoneBookItem item: itemsCopy){
if(item.name.toLowerCase().contains(text) || item.phone.toLowerCase().contains(text)){
items.add(item);
}
}
}
notifyDataSetChanged();
}
itemsCopy
はitemsCopy.addAll(items)
のようにアダプタのコンストラクタで初期化されます。
その場合は、filter
からOnQueryTextListener
を呼び出すだけです。
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
adapter.filter(query);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
adapter.filter(newText);
return true;
}
});
電話帳を名前と電話番号でフィルタリングした例です。
@Shruthi Kamojiをクリーンな方法で使用した後は、フィルタ処理可能なフィルタを使用できます。
public abstract class GenericRecycleAdapter<E> extends RecyclerView.Adapter implements Filterable
{
protected List<E> list;
protected List<E> originalList;
protected Context context;
public GenericRecycleAdapter(Context context,
List<E> list)
{
this.originalList = list;
this.list = list;
this.context = context;
}
...
@Override
public Filter getFilter() {
return new Filter() {
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list = (List<E>) results.values;
notifyDataSetChanged();
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<E> filteredResults = null;
if (constraint.length() == 0) {
filteredResults = originalList;
} else {
filteredResults = getFilteredResults(constraint.toString().toLowerCase());
}
FilterResults results = new FilterResults();
results.values = filteredResults;
return results;
}
};
}
protected List<E> getFilteredResults(String constraint) {
List<E> results = new ArrayList<>();
for (E item : originalList) {
if (item.getName().toLowerCase().contains(constraint)) {
results.add(item);
}
}
return results;
}
}
ここのEはジェネリック型です。クラスを使って拡張することができます。
public class customerAdapter extends GenericRecycleAdapter<CustomerModel>
あるいは単にEをあなたが望むタイプに変更してください(例えば<CustomerModel>
)
それからsearchView(menu.xmlに置くことができるウィジェット)から:
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String text) {
return false;
}
@Override
public boolean onQueryTextChange(String text) {
yourAdapter.getFilter().filter(text);
return true;
}
});
単に一つのorignalと一つのtempとFilterableを実装するの2つのリストを作成します。
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
final FilterResults oReturn = new FilterResults();
final ArrayList<T> results = new ArrayList<>();
if (origList == null)
origList = new ArrayList<>(itemList);
if (constraint != null && constraint.length() > 0) {
if (origList != null && origList.size() > 0) {
for (final T cd : origList) {
if (cd.getAttributeToSearch().toLowerCase()
.contains(constraint.toString().toLowerCase()))
results.add(cd);
}
}
oReturn.values = results;
oReturn.count = results.size();//newly Aded by ZA
} else {
oReturn.values = origList;
oReturn.count = origList.size();//newly added by ZA
}
return oReturn;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(final CharSequence constraint,
FilterResults results) {
itemList = new ArrayList<>((ArrayList<T>) results.values);
// FIXME: 8/16/2017 implement Comparable with sort below
///Collections.sort(itemList);
notifyDataSetChanged();
}
};
}
どこで
public GenericBaseAdapter(Context mContext, List<T> itemList) {
this.mContext = mContext;
this.itemList = itemList;
this.origList = itemList;
}
Androidアーキテクチャコンポーネントを使用してLiveDataを使用すると、これはあらゆるタイプのAdapterを使用して簡単に実装できます。次の手順を実行するだけです。
1.次の例のように、RoomDatabaseからLiveDataとして返されるようにデータを設定します。
@Dao
public interface CustomDAO{
@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
public LiveData<List<Word>> searchFor(String searchquery);
}
2.ViewModelオブジェクトを作成して、あなたのDAOとあなたのUIを結びつける方法でライブデータを更新します。
public class CustomViewModel extends AndroidViewModel {
private final AppDatabase mAppDatabase;
public WordListViewModel(@NonNull Application application) {
super(application);
this.mAppDatabase = AppDatabase.getInstance(application.getApplicationContext());
}
public LiveData<List<Word>> searchQuery(String query) {
return mAppDatabase.mWordDAO().searchFor(query);
}
}
3.以下のようにonQueryTextListenerを通じてクエリを渡すことで、ViewModelからその場でデータを呼び出します。
onCreateOptionsMenu
の中で、リスナーを次のように設定します。
searchView.setOnQueryTextListener(onQueryTextListener);
次のようにSearchActivityクラスのどこかにクエリリスナを設定します。
private Android.support.v7.widget.SearchView.OnQueryTextListener onQueryTextListener =
new Android.support.v7.widget.SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
getResults(query);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
getResults(newText);
return true;
}
private void getResults(String newText) {
String queryText = "%" + newText + "%";
mCustomViewModel.searchQuery(queryText).observe(
SearchResultsActivity.this, new Observer<List<Word>>() {
@Override
public void onChanged(@Nullable List<Word> words) {
if (words == null) return;
searchAdapter.submitList(words);
}
});
}
};
注:ステップ(1.)と(2.)は標準のAAC ViewModelとDAOの実装です。ここで行われる唯一の本当の「魔法」はにありますOnQueryTextListenerこれは、クエリテキストが変更されたときにリストの結果を動的に更新します。
あなたがその問題についてさらに説明を必要とするならば、遠慮なく尋ねてください。これが助けになれば幸いです:)。
検索後のテキストをクリアした後(フィルタが機能しなくなった)、フィルタリストよりもサイズが小さく、IndexOutOfBoundsExceptionが発生したために問題が起こらないように、@ Xaver Kapellerの解決策を以下の2つに変更してください。そのため、コードは以下のように修正する必要があります。
public void addItem(int position, ExampleModel model) {
if(position >= mModel.size()) {
mModel.add(model);
notifyItemInserted(mModel.size()-1);
} else {
mModels.add(position, model);
notifyItemInserted(position);
}
}
そしてmoveItem機能でも修正する
public void moveItem(int fromPosition, int toPosition) {
final ExampleModel model = mModels.remove(fromPosition);
if(toPosition >= mModels.size()) {
mModels.add(model);
notifyItemMoved(fromPosition, mModels.size()-1);
} else {
mModels.add(toPosition, model);
notifyItemMoved(fromPosition, toPosition);
}
}
それはあなたを助けることができることを願っています!
これは私がフィルタリングアニメーションを失うことのないように@klimatの答えを拡張しているところです。
public void filter(String query){
int completeListIndex = 0;
int filteredListIndex = 0;
while (completeListIndex < completeList.size()){
Movie item = completeList.get(completeListIndex);
if(item.getName().toLowerCase().contains(query)){
if(filteredListIndex < filteredList.size()) {
Movie filter = filteredList.get(filteredListIndex);
if (!item.getName().equals(filter.getName())) {
filteredList.add(filteredListIndex, item);
notifyItemInserted(filteredListIndex);
}
}else{
filteredList.add(filteredListIndex, item);
notifyItemInserted(filteredListIndex);
}
filteredListIndex++;
}
else if(filteredListIndex < filteredList.size()){
Movie filter = filteredList.get(filteredListIndex);
if (item.getName().equals(filter.getName())) {
filteredList.remove(filteredListIndex);
notifyItemRemoved(filteredListIndex);
}
}
completeListIndex++;
}
}
基本的には、完全なリストを調べ、フィルタされたリストにアイテムを1つずつ追加/削除します。
アダプター内:
public void setFilter(List<Channel> newList){
mChannels = new ArrayList<>();
mChannels.addAll(newList);
notifyDataSetChanged();
}
活動中:
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
newText = newText.toLowerCase();
ArrayList<Channel> newList = new ArrayList<>();
for (Channel channel: channels){
String channelName = channel.getmChannelName().toLowerCase();
if (channelName.contains(newText)){
newList.add(channel);
}
}
mAdapter.setFilter(newList);
return true;
}
});