SearchViewでRecyclerViewをフィルターする方法


319

SearchViewサポートライブラリからを実装しようとしています。ユーザーがを使用しSearchViewListの映画をフィルタリングできるようにしたい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);
        }
    }
}

回答:


913

前書き

何が問題になっているのかは質問からは明確ではないので、この機能を実装する方法について簡単に説明しました。それでも質問がある場合は、遠慮なく質問してください。

このGitHubリポジトリには、ここで話しているすべての実例があります
サンプルプロジェクトについて詳しく知りたい場合は、プロジェクトのホームページにアクセスしてください。

いずれにしても、結果は次のようになります。

デモ画像

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

Google Playで入手する

とにかく始めましょう。


のセットアップ SearchView

フォルダ内にres/menuという新しいファイルを作成しますmain_menu.xml。その中にアイテムを追加し、をに設定actionViewClassandroid.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();
    }
}

以下のようなコールバックの上部の方法ではonMovedonInsertedなど、あなたの方法を通知同等のものを呼び出す必要がありますAdapter。3つの下部の方法compareareContentsTheSameそして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そこにある場合、再度追加されないことです。代わりに、SortedListareContentsTheSame()メソッドを使用して、オブジェクトが変更されたかどうか、およびオブジェクトがにあるかどうかを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();
}

この方法でも、すべての更新をバッチ処理してパフォーマンスを向上させます。最初のループは逆です。最初にアイテムを削除すると、その後に表示されるすべてのアイテムのインデックスが混乱し、場合によってはデータの不整合などの問題が発生する可能性があるためです。後私達はちょうど追加することListSortedList使用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すべての可能なモデルを定義する必要があります。この例では、私は作成ListExampleModel映画の配列からインスタンスを:

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ます。その後ListMOVIES配列の映画名からモデルのを作成します。次に、すべてのモデルをに追加します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-この場合はExampleModelperformBind()ではなく、モデルにデータをバインドする必要があります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()、変更は適用されません。


4
@TiagoOliveiraまあそれは箱から出してすぐに動作するように作られています:Dデータバインディングはそれに慣れていない人々にとってハードルですが、それは素晴らしいのでそれを宣伝したいのでとにかくそれを含めました。どういうわけかそれについて多くの人々が知らないようです...
Xaver Kapeller '31

78
私はまだすべての回答を読んでいません。このコメントを書くには、読みを半分に止めなければなりませんでした。これは、SOで見つけた最良の回答の1つです。ありがとう!
daneejela 2016年

16
「あなたが問題を抱えているのは質問からは明らかではないので、ここに私がやった完全な例を示します」:D
Fred

7
+1は、Androidにデータバインディングが存在することを示すだけです。それについて聞いたことがなく、使い始めるようです。ありがとう
ホルヘカサリエゴ2017

6
このソリューションは途方もなく長く、一般的に言って過度に設計されています。二番目に行く。
Enrico Casini

194

あなたがする必要があるのは、にfilterメソッドを追加することですRecyclerView.Adapter

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)ます。

その場合は、次から呼び出しfilterOnQueryTextListenerください。

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;
    }
});

これは、名前と電話番号で電話帳をフィルタリングした例です。


11
これは受け入れられる答えだと思います。よりシンプルで機能します
Jose_GD

6
シンプルで効率的!
AlxDroidDev 2017

11
@Xaver Kapellerの回答ではなく、このアプローチに従うと、アニメーションが失われることに注意してください。
2017

23
あまりにも長いので、受け入れられた答えを試さなかった。この答えは機能し、簡単に実装できます。メニュー項目XMLに「app:actionViewClass = "android.support.v7.widget.SearchView」を追加することを忘れないでください
SajithK

3
正確にはitemsとitemsCopyは何ですか?
Lucky_girl

82

より明確な方法で@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;
    }
});

私はこのようなものを使います!正常で一般的なサンプルで動作します!
マテウス2016

:この1とステップ-Y-ステップ私を助けることができる人、こんにちはstackoverflow.com/questions/40754174/...
Thorvald Olavsen

一番きれいな答え!
adalpari 2017年

4
操作は、performFilteringメソッドのワーカースレッドで行われるため、賛成票よりもはるかに優れています。
うーん2018年

1
ただし、同じリストへの参照を異なる変数に割り当てます。たとえば、this.originalList = list; 代わりにaddAllを使用するか、ArrayListコンストラクターでリストを渡す必要があります
Florian Walther

5

アダプタに2つのリストを作成し、1つはオリジナル、もう1つは一時的で、Filterable実装します

    @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;
    }

素晴らしい解決策。2つのリストを作成し、単純なフィルターメソッドを使用しました。アイテムの正しいアダプター位置を次のアクティビティに渡すことができないようです。これについて提案できる考えやアイデアがあれば幸いです。stackoverflow.com
questions

4

アダプター:

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;
            }
        });

3

Androidのアーキテクチャのコンポーネントを使用してLiveDataこれは簡単に任意のタイプで実現することができるアダプタ。次の手順を実行するだけです。

1.以下の例のように、Room データベースからLiveDataとして返されるようにデータを設定します。

@Dao
public interface CustomDAO{

@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
    public LiveData<List<Word>> searchFor(String searchquery);
}

2. ViewModelオブジェクトを作成して、DAOUIを接続するメソッドを通じてデータをライブで更新します

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 ViewModelDAOの実装です。ここで行われる唯一の「マジック」は、クエリテキストが変更されるとリストの結果を動的に更新するOnQueryTextListenerにあります。

問題についてさらに説明が必要な場合は、遠慮なくご質問ください。これがお役に立てば幸いです。


1

これは、フィルタリングアニメーションを失わないように@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つずつ追加/削除します。


0

@Xaver Kapellerのソリューションを以下の2つに変更して、検索されたテキストをクリアした後(フィルターが機能しなくなった)アダプターのリストのサイズがフィルターリストよりも小さく、IndexOutOfBoundsExceptionが発生したため、問題を回避することをお勧めします。したがって、コードは以下のように変更する必要があります

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); 
    }
}

それがあなたを助けることを願っています!


それはまったく必要ありません。
Xaver Kapeller

元の回答では、そうしないとIndexOutOfBoundsExceptionが発生するので、なぜ必要ないのでしょうか???? ログが必要ですか?@XaverKapeller
toidv

例外はAdapter、間違った方法で実装した場合にのみ発生します。コードが表示されない場合、最も可能性の高い問題は、すべてのアイテムを含むリストのコピーをに渡していないことですAdapter
Xaver Kapeller

エラーログ:W / System.err:java.lang.IndexOutOfBoundsException:Invalid index 36、size is 35 W / System.err:at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)W / System.err: java.util.ArrayList.add(ArrayList.java:147)でW / System.err:com.quomodo.inploi.ui.adapter.MultipleSelectFilterAdapter.addItem(MultipleSelectFilterAdapter.java:125)でW / System.err:comで.quomodo.inploi.ui.adapter.MultipleSelectFilterAdapter.applyAndAnimateAdditions(MultipleSelectFilterAdapter.java:78)
toidv


0

searchviewとclicklistenerを備えたRecyclerview

アダプターにインターフェースを追加します。

public interface SelectedUser{

    void selectedUser(UserModel userModel);

}

メインアクティビティにインターフェースを実装し、メソッドをオーバーライドします。@Override public void selectedUser(UserModel userModel){

    startActivity(new Intent(MainActivity.this, SelectedUserActivity.class).putExtra("data",userModel));



}

完全なチュートリアルとソースコード: searchviewとonclicklistenerを備えたRecyclerview


-1

いくつかの変更を加えたリンクを使用して同じ問題を解決しました。カード付きのRecyclerViewの検索フィルター。可能ですか?(お役に立てれば)。

これが私のアダプタクラスです

public class ContactListRecyclerAdapter extends RecyclerView.Adapter<ContactListRecyclerAdapter.ContactViewHolder> implements Filterable {

Context mContext;
ArrayList<Contact> customerList;
ArrayList<Contact> parentCustomerList;


public ContactListRecyclerAdapter(Context context,ArrayList<Contact> customerList)
{
    this.mContext=context;
    this.customerList=customerList;
    if(customerList!=null)
    parentCustomerList=new ArrayList<>(customerList);
}

   // other overrided methods

@Override
public Filter getFilter() {
    return new FilterCustomerSearch(this,parentCustomerList);
}
}

//フィルタークラス

import android.widget.Filter;
import java.util.ArrayList;


public class FilterCustomerSearch extends Filter
{
private final ContactListRecyclerAdapter mAdapter;
ArrayList<Contact> contactList;
ArrayList<Contact> filteredList;

public FilterCustomerSearch(ContactListRecyclerAdapter mAdapter,ArrayList<Contact> contactList) {
    this.mAdapter = mAdapter;
    this.contactList=contactList;
    filteredList=new ArrayList<>();
}

@Override
protected FilterResults performFiltering(CharSequence constraint) {
    filteredList.clear();
    final FilterResults results = new FilterResults();

    if (constraint.length() == 0) {
        filteredList.addAll(contactList);
    } else {
        final String filterPattern = constraint.toString().toLowerCase().trim();

        for (final Contact contact : contactList) {
            if (contact.customerName.contains(constraint)) {
                filteredList.add(contact);
            }
            else if (contact.emailId.contains(constraint))
            {
                filteredList.add(contact);

            }
            else if(contact.phoneNumber.contains(constraint))
                filteredList.add(contact);
        }
    }
    results.values = filteredList;
    results.count = filteredList.size();
    return results;
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
    mAdapter.customerList.clear();
    mAdapter.customerList.addAll((ArrayList<Contact>) results.values);
    mAdapter.notifyDataSetChanged();
}

}

//アクティビティクラス

public class HomeCrossFadeActivity extends AppCompatActivity implements View.OnClickListener,OnFragmentInteractionListener,OnTaskCompletedListner
{
Fragment fragment;
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_homecrossfadeslidingpane2);CardView mCard;
   setContentView(R.layout.your_main_xml);}
   //other overrided methods
  @Override
   public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.

    MenuInflater inflater = getMenuInflater();
    // Inflate menu to add items to action bar if it is present.
    inflater.inflate(R.menu.menu_customer_view_and_search, menu);
    // Associate searchable configuration with the SearchView
    SearchManager searchManager =
            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView =
            (SearchView) menu.findItem(R.id.menu_search).getActionView();
    searchView.setQueryHint("Search Customer");
    searchView.setSearchableInfo(
            searchManager.getSearchableInfo(getComponentName()));

    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            return false;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            if(fragment instanceof CustomerDetailsViewWithModifyAndSearch)
                ((CustomerDetailsViewWithModifyAndSearch)fragment).adapter.getFilter().filter(newText);
            return false;
        }
    });



    return true;
}
}

OnQueryTextChangeListener()メソッドでは、アダプタを使用します。私のアダプターが断片化しているので、それを断片化するためにキャストしました。アダプタがアクティビティクラスにある場合は、アダプタを直接使用できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.