web-dev-qa-db-ja.com

SearchViewでRecyclerViewをフィルタリングする方法

サポートライブラリからSearchViewを実装しようとしています。私は、ユーザーがSearchView内の映画のListをフィルタリングするためにRecyclerViewを使用することを望んでいます。

私はこれまでにいくつかのチュートリアルをたどり、SearchViewActionBarに追加しましたが、ここからどこへ行くべきかはよくわかりません。いくつか例を見てきましたが、入力しても結果が表示されません。

これは私の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);
        }
    }
}
279
Jacques Krause

前書き

あなたの質問からあなたが何に問題を抱えているのかが本当に明確ではないので、この機能を実装する方法についてのこのクイックウォークスルーを書きました。まだ質問がある場合はお気軽にお問い合わせください。

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

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

demo image

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

Get it on Google Play

とにかく始めましょう。


SearchViewのセットアップ

フォルダーres/menumain_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を使用する準備が整いました。 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

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

onMovedonInsertedなどのコールバックの上部にあるメソッドでは、Adapterの同等の通知メソッドを呼び出す必要があります。一番下のcompareareContentsTheSame、および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 St​​udio、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にある場合、それは再び追加されないということです。代わりに、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();
}

このメソッドは、すべての更新をまとめてバッチ処理して、パフォーマンスを向上させます。最初のループは逆になります。開始時にアイテムを削除すると、その後に出現するすべてのアイテムのインデックスが台無しになり、データの不整合などの問題が発生する可能性があるためです。その後、_addAll()を使用してListSortedListを追加し、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()を呼び出し、ListsのExampleModelとクエリ文字列を渡します。次に、AdapterreplaceAll()を呼び出し、filter()によって返されたフィルター処理されたListを渡します。また、RecyclerViewscrollToPosition(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の複雑な詳細について心配する必要はありません。

SortedListAdapter

StackOverflowの回答には文字数制限があるため、この基本クラスを実装する各ステップを実行することも、ここに完全なソースコードを追加することもできませんが、この基本クラスの完全なソースコードを見つけることができます-SortedListAdapter-in this GitHub Gist

あなたの人生を簡単にするために、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には、元の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()の呼び出しを忘れると、変更は適用されません!

863
Xaver Kapeller

RecyclerView.Adapterfilterメソッドを追加するだけです。

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

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

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

164
klimat

@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;
    }
});
65
sagits

単に一つの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;
    }
5
Xar E Ahmer

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

あなたがその問題についてさらに説明を必要とするならば、遠慮なく尋ねてください。これが助けになれば幸いです:)。

1
Panos Gr

検索後のテキストをクリアした後(フィルタが機能しなくなった)、フィルタリストよりもサイズが小さく、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); 
    }
}

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

0
toidv

これは私がフィルタリングアニメーションを失うことのないように@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
AhmadF

アダプター内:

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;
            }
        });
0
Firoz Ahmed