Introdução
Como não está realmente claro na sua pergunta com o que exatamente você está tendo problemas, escrevi esta rápida explicação sobre como implementar esse recurso; Se você ainda tiver dúvidas, não hesite em perguntar.
Eu tenho um exemplo prático de tudo o que estou falando aqui neste Repositório do GitHub .
Se você quiser saber mais sobre o projeto de exemplo, visite a página inicial do projeto .
De qualquer forma, o resultado deve ser algo como isto:

Se você deseja primeiro brincar com o aplicativo demo, pode instalá-lo na Play Store:

De qualquer forma, vamos começar.
Configurando o SearchView
Na pasta, res/menucrie um novo arquivo chamado main_menu.xml. Nele, adicione um item e defina actionViewClasscomo android.support.v7.widget.SearchView. Como você está usando a biblioteca de suporte, é necessário usar o espaço para nome da biblioteca de suporte para definir o actionViewClassatributo. Seu arquivo xml deve ser algo como isto:
<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>
No seu Fragmentou Activityvocê precisa inflar esse menu em xml como de costume, pode procurar o MenuItemque contém o SearchViewe implementar o OnQueryTextListenerque vamos usar para ouvir as alterações no texto digitado no 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;
}
E agora o SearchViewestá pronto para ser usado. Implementaremos a lógica do filtro mais tarde, onQueryTextChange()assim que terminarmos de implementar o Adapter.
Configurando o Adapter
Em primeiro lugar, esta é a classe de modelo que vou usar neste exemplo:
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;
}
}
É apenas o seu modelo básico que exibirá um texto no RecyclerView. Este é o layout que vou usar para exibir o texto:
<?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>
Como você pode ver, eu uso Ligação de Dados. Se você nunca trabalhou com ligação de dados antes, não desanime! É muito simples e poderoso, no entanto, não posso explicar como isso funciona no escopo desta resposta.
Este é o ViewHolderda ExampleModelclasse:
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);
}
}
Mais uma vez nada de especial. Ele apenas usa ligação de dados para vincular a classe de modelo a esse layout, conforme definimos no xml de layout acima.
Agora podemos finalmente chegar à parte realmente interessante: escrever o adaptador. Vou pular a implementação básica do Adaptere, em vez disso, vou me concentrar nas partes que são relevantes para esta resposta.
Mas primeiro há uma coisa sobre a qual devemos falar: a SortedListclasse.
SortedList
O SortedListé uma ferramenta completamente incrível que faz parte da RecyclerViewbiblioteca. Ele cuida de notificar as Adapteralterações sobre o conjunto de dados e faz isso de uma maneira muito eficiente. A única coisa que você precisa fazer é especificar uma ordem dos elementos. Você precisa fazer isso implementando um compare()método que compara dois elementos da SortedListmesma forma que a Comparator. Mas, em vez de classificar um List, é usado para classificar os itens no RecyclerView!
O SortedListinterage com a classe Adapterthrough Callbackque você precisa implementar:
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();
}
}
Nos métodos na parte superior do retorno de chamada onMoved, como onInserted, etc., você deve chamar o método de notificação equivalente ao seu Adapter. Os três métodos na parte inferior compare, areContentsTheSamee areItemsTheSamevocê tem que implementar de acordo com o tipo de objetos que você deseja exibir e em que ordem esses objetos devem aparecer na tela.
Vamos passar por esses métodos, um por um:
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
Este é o compare()método sobre o qual falei anteriormente. Neste exemplo, estou apenas passando a chamada para a Comparatorque compara os dois modelos. Se você deseja que os itens apareçam em ordem alfabética na tela. Esse comparador pode ficar assim:
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
Agora vamos dar uma olhada no próximo método:
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
O objetivo deste método é determinar se o conteúdo de um modelo foi alterado. Ele SortedListusa isso para determinar se um evento de mudança precisa ser chamado - em outras palavras, se o RecyclerViewcrossfade deve ser feito entre a versão antiga e a nova. Se você modelar classes com uma correta equals()e hashCode()implementação, geralmente poderá implementá-la como acima. Se somarmos um equals()e hashCode()implementação da ExampleModelclasse deve ser algo como isto:
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;
}
}
Nota lateral rápida: a maioria dos IDE, como Android Studio, IntelliJ e Eclipse, tem funcionalidade para gerar equals()e hashCode()implementações para você com o pressionar de um botão! Então você não precisa implementá-los você mesmo. Procure na internet como funciona no seu IDE!
Agora vamos dar uma olhada no último método:
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
O SortedListusa esse método para verificar se dois itens se referem à mesma coisa. Em termos mais simples (sem explicar como SortedListfunciona), isso é usado para determinar se um objeto já está contido na Listanimação e se é necessário reproduzir uma animação de adicionar, mover ou alterar. Se seus modelos tiverem um ID, você normalmente compararia apenas o ID neste método. Caso contrário, você precisa descobrir outra maneira de verificar isso, mas, no entanto, você acaba implementando isso depende do seu aplicativo específico. Geralmente, é a opção mais simples de fornecer um ID a todos os modelos - que poderia, por exemplo, ser o campo da chave primária se você estiver consultando os dados de um banco de dados.
Com o SortedList.Callbackimplementado corretamente, podemos criar uma instância do SortedList:
final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);
Como o primeiro parâmetro no construtor do, SortedListvocê precisa passar a classe de seus modelos. O outro parâmetro é exatamente o SortedList.Callbackque definimos acima.
Agora, vamos ao que interessa: se implementarmos o Adaptercom um SortedList, deve ser algo como isto:
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();
}
}
O Comparatorusado para classificar o item é passado pelo construtor, para que possamos usar o mesmo, Adaptermesmo que os itens devam ser exibidos em uma ordem diferente.
Agora estamos quase terminando! Mas primeiro precisamos de uma maneira de adicionar ou remover itens ao arquivo Adapter. Para esse fim, podemos adicionar métodos aos Adapterquais nos permitem adicionar e remover itens ao 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();
}
Não precisamos chamar nenhum método de notificação aqui, porque o SortedListjá faz isso por meio do SortedList.Callback! Além disso, a implementação desses métodos é bastante direta, com uma exceção: o método remove, que remove um Listdos modelos. Como o SortedListmétodo remove apenas um que pode remover um único objeto, precisamos percorrer a lista e remover os modelos um a um. Chamar beginBatchedUpdates()no início lotes de todas as alterações que faremos no SortedListconjunto e melhorar o desempenho. Quando chamamos endBatchedUpdates()o RecyclerViewé notificado sobre todas as alterações de uma só vez.
Além disso, o que você precisa entender é que se você adicionar um objeto ao SortedListe ele já estiver SortedListnele, ele não será adicionado novamente. Em vez disso, SortedListusa o areContentsTheSame()método para descobrir se o objeto foi alterado - e se ele possui o item no RecyclerViewserá atualizado.
De qualquer forma, o que eu normalmente prefiro é um método que me permite substituir todos os itens de RecyclerViewuma só vez. Remova tudo o que não está no Liste adicione todos os itens que estão faltando no 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();
}
Esse método novamente agrupa todas as atualizações para aumentar o desempenho. O primeiro loop é inverso, pois a remoção de um item no início atrapalhava os índices de todos os itens que surgiram depois e isso pode levar, em alguns casos, a problemas como inconsistências de dados. Depois disso, basta adicionar o Listao SortedListuso addAll()de adicionar todos os itens que já não estão no SortedListe - assim como eu descrevi acima - atualização todos os itens que já estão no SortedListmas foram alterados.
E com isso o Adapterestá completo. A coisa toda deve se parecer com isso:
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();
}
}
A única coisa que falta agora é implementar a filtragem!
Implementando a lógica do filtro
Para implementar a lógica do filtro, primeiro precisamos definir um Listde todos os modelos possíveis. Para este exemplo, crio uma Listde ExampleModelinstâncias a partir de uma matriz de filmes:
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);
}
Nada de especial está acontecendo aqui, apenas instanciamos Adaptere definimos como RecyclerView. Depois disso, criamos um Listdos modelos a partir dos nomes dos filmes na MOVIESmatriz. Em seguida, adicionamos todos os modelos ao arquivo SortedList.
Agora podemos voltar ao onQueryTextChange()que definimos anteriormente e começar a implementar a lógica do filtro:
@Override
public boolean onQueryTextChange(String query) {
final List<ExampleModel> filteredModelList = filter(mModels, query);
mAdapter.replaceAll(filteredModelList);
mBinding.recyclerView.scrollToPosition(0);
return true;
}
Isso é novamente bastante direto. Chamamos o método filter()e passamos o Listde ExampleModels e a string de consulta. Chamamos então replaceAll()no Adaptere passar o filtrado Listretornado por filter(). Também precisamos chamar scrollToPosition(0)o RecyclerViewpara garantir que o usuário sempre possa ver todos os itens ao procurar algo. Caso contrário, ele RecyclerViewpoderá permanecer em uma posição rolada para baixo durante a filtragem e subsequentemente ocultar alguns itens. Rolar para o topo garante uma melhor experiência do usuário durante a pesquisa.
A única coisa que resta a fazer agora é se implementar 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;
}
A primeira coisa que fazemos aqui é chamar toLowerCase()a string de consulta. Não queremos que nossa função de toLowerCase()pesquisa faça distinção entre maiúsculas e minúsculas e, chamando todas as sequências comparadas, podemos garantir que retornemos os mesmos resultados, independentemente do caso. Ele então itera através de todos os modelos no Listque passamos para ele e verifica se a string de consulta está contida no texto do modelo. Se for, o modelo é adicionado ao filtrado List.
E é isso! O código acima será executado no nível 7 da API e acima. A partir do nível 11 da API, você obtém animações de itens de graça!
Percebo que esta é uma descrição muito detalhada que provavelmente faz com que tudo pareça mais complicado do que realmente é, mas existe uma maneira de generalizar todo esse problema e tornar a implementação Adapterbaseada em uma SortedListmuito mais simples.
Generalizando o Problema e Simplificando o Adaptador
Nesta seção, não entrarei em muitos detalhes - em parte porque estou correndo contra o limite de caracteres para obter respostas no Stack Overflow, mas também porque a maioria já foi explicada acima -, mas para resumir as mudanças: Podemos implementar uma Adapterclasse base que já cuida de lidar com os SortedListmodelos, bem como vincula as ViewHolderinstâncias e fornece uma maneira conveniente de implementar uma Adapterbaseada em a SortedList. Para isso, temos que fazer duas coisas:
- Precisamos criar uma
ViewModelinterface que todas as classes de modelo precisam implementar
- Precisamos criar uma
ViewHoldersubclasse que defina um bind()método que Adapterpode ser usado para vincular modelos automaticamente.
Isso nos permite focar apenas no conteúdo que deve ser exibido no RecyclerViewapenas implementando os modelos e as ViewHolderimplementações correspondentes . Usando esta classe base, não precisamos nos preocupar com os detalhes intrincados do Adaptere dele SortedList.
SortedListAdapter
Devido ao limite de caracteres para respostas no StackOverflow, não posso executar cada etapa da implementação dessa classe base ou até mesmo adicionar o código fonte completo aqui, mas você pode encontrar o código fonte completo dessa classe base - como eu chamei SortedListAdapter- neste GitHub Gist .
Para simplificar sua vida, publiquei uma biblioteca no jCenter que contém o SortedListAdapter! Se você deseja usá-lo, basta adicionar essa dependência ao arquivo build.gradle do seu aplicativo:
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
Você pode encontrar mais informações sobre esta biblioteca na página inicial da biblioteca .
Usando o SortedListAdapter
Para usar o SortedListAdapter, temos que fazer duas alterações:
Altere o ViewHolderpara que ele se estenda SortedListAdapter.ViewHolder. O parâmetro type deve ser o modelo que deve ser associado a isso ViewHolder- neste caso ExampleModel. Você precisa vincular dados aos seus modelos em performBind()vez de 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);
}
}
Verifique se todos os seus modelos implementam a ViewModelinterface:
public class ExampleModel implements SortedListAdapter.ViewModel {
...
}
Depois disso, basta atualizar o ExampleAdapterarquivo para estender SortedListAdaptere remover tudo o que não precisamos mais. O parâmetro type deve ser o tipo de modelo com o qual você está trabalhando - neste caso ExampleModel. Mas se você estiver trabalhando com diferentes tipos de modelos, em seguida, definir o tipo de parâmetro para 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);
}
}
Depois que terminamos! No entanto, uma última coisa a mencionar: o SortedListAdapternão possui o mesmo add(), remove()ou os replaceAll()métodos que nosso original ExampleAdapterpossuía. Ele usa um Editorobjeto separado para modificar os itens da lista que podem ser acessados através do edit()método Portanto, se você deseja remover ou adicionar itens aos quais você precisa ligar edit(), adicione e remova os itens nessa Editorinstância e, quando terminar, chame commit()-o para aplicar as alterações no SortedList:
mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();
Todas as alterações feitas dessa maneira são agrupadas em lote para aumentar o desempenho. O replaceAll()método que implementamos nos capítulos acima também está presente neste Editorobjeto:
mAdapter.edit()
.replaceAll(mModels)
.commit();
Se você esquecer de ligar commit(), nenhuma das suas alterações será aplicada!