Jak filtrować podgląd RecyclerView za pomocą widoku wyszukiwania
Próbuję zaimplementować SearchView
z biblioteki wsparcia. Chcę, aby użytkownik był używać SearchView
do filtrowania List
filmów w RecyclerView
.
Do tej pory wykonałem kilka samouczków i dodałem SearchView
do ActionBar
, ale nie jestem do końca pewien, dokąd się stąd udać. Widziałem kilka przykładów, ale żaden z nich nie pokazuje wyników podczas pisania.
To jest moje 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);
}
}
A to jest moje 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);
}
}
}
7 answers
Wprowadzenie
Ponieważ nie jest to bardzo jasne forma pytania z czym dokładnie masz problemy, napisałem ten krótki przegląd o tym, jak wdrożyć tę funkcję, jeśli nadal masz pytania, nie krępuj się zadać.
Mam działający przykład wszystkiego, o czym mówię tutaj w tym repozytorium GitHub.
Jeśli chcesz dowiedzieć się więcej o przykładowym projekcie odwiedź projekt Strona główna.
W każdym przypadku wynik powinien wyglądać mniej więcej tak:
Jeśli chcesz najpierw pobawić się z aplikacją demo, możesz ją zainstalować w Sklepie Play:]}W każdym razie zaczynajmy.
Tworzenie SearchView
W folderze res/menu
utwórz nowy plik o nazwie main_menu.xml
. W nim Dodaj element i ustaw actionViewClass
na android.support.v7.widget.SearchView
. Ponieważ korzystasz z pomocy technicznej biblioteka musisz użyć przestrzeni nazw biblioteki wsparcia, aby ustawić atrybut actionViewClass
. Twój plik xml powinien wyglądać mniej więcej tak:
<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>
W twoim Fragment
lub Activity
musisz napompować to Menu XML jak zwykle, następnie możesz poszukać MenuItem
, który zawiera SearchView
i zaimplementować OnQueryTextListener
, którego użyjemy do nasłuchania zmian w tekście wprowadzonym do 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) MenuItemCompat.getActionView(searchItem);
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;
}
A teraz {[25] } jest gotowy do użycia. Zaimplementujemy logikę filtra później w onQueryTextChange()
Po zakończeniu implementacji Adapter
.
Tworzenie Adapter
Przede wszystkim jest to klasa modelu, której użyję w tym przykładzie:
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;
}
}
To tylko twój podstawowy model, który wyświetli tekst w RecyclerView
. Jest to układ, którego użyję do wyświetlenia tekstu:
<?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>
Jak widzisz używam wiązania danych. Jeśli nigdy wcześniej nie pracowałeś z powiązaniami danych, nie zniechęcaj się! Jest bardzo proste i potężne, jednak nie potrafię wyjaśnić, jak to działa w zakresie tej odpowiedzi.
To jest ViewHolder
dla ExampleModel
klasy:
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);
}
}
Znowu nic specjalnego. Po prostu używa wiązania danych, aby powiązać klasę modelu z tym układem, jak zdefiniowaliśmy w powyższym układzie XML.
Teraz możemy wreszcie przejść do naprawdę interesującej części: pisania adaptera. Mam zamiar pominąć podstawową implementację Adapter
i zamiast tego zamierzam skoncentrować się na części, które są istotne dla tej odpowiedzi.
Ale najpierw jest jedna rzecz, o której musimy porozmawiać: SortedList
klasy.
SortedList
SortedList
jest całkowicie niesamowitym narzędziem, które jest częścią RecyclerView
biblioteki. Dba o powiadomienie Adapter
o zmianach w zbiorze danych i robi to w bardzo skuteczny sposób. Jedyne, co musisz zrobić, to określić kolejność elementów. Musisz to zrobić poprzez wdrożenie compare()
metoda, która porównuje dwa elementy w SortedList
tak jak Comparator
. Ale zamiast sortowania List
jest on używany do sortowania elementów w RecyclerView
!
SortedList
współdziała z Adapter
poprzez Callback
klasę, którą musisz zaimplementować:
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();
}
}
W metodach na górze wywołania zwrotnego jak onMoved
, onInserted
, itd. musisz wywołać równoważną metodę powiadamiania Adapter
. Trzy metody na dole compare
, areContentsTheSame
i areItemsTheSame
musisz wdrożyć zgodnie do jakiego rodzaju obiektów chcesz wyświetlić i w jakiej kolejności obiekty te powinny pojawić się na ekranie.
Przejdźmy przez te metody jeden po drugim:
@Override
public int compare(ExampleModel a, ExampleModel b) {
return mComparator.compare(a, b);
}
To jest Metoda, o której mówiłem wcześniej. W tym przykładzie po prostu przekazuję wywołanie Comparator
, które porównuje oba modele. Jeśli chcesz, aby elementy były wyświetlane w porządku alfabetycznym na ekranie. Ten komparator może wyglądać tak:
private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
@Override
public int compare(ExampleModel a, ExampleModel b) {
return a.getText().compareTo(b.getText());
}
};
Teraz spójrzmy na następny metoda:
@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
return oldItem.equals(newItem);
}
Celem tej metody jest ustalenie, czy zawartość modelu uległa zmianie. SortedList
używa tego do określenia, czy zdarzenie change musi zostać wywołane - innymi słowy, jeśli {[41] } powinno skrzyżować starą i nową wersję. Jeśli modelowe klasy mają poprawną implementację equals()
i hashCode()
, zazwyczaj możesz ją zaimplementować tak jak powyżej. Jeśli dodamy implementację equals()
i hashCode()
do klasy ExampleModel
powinna ona wyglądać tak: to:
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;
}
}
Szybka Uwaga: Większość IDE, takich jak Android Studio, IntelliJ i Eclipse mają funkcjonalność do generowania equals()
i {68]} implementacje dla Ciebie za naciśnięciem przycisku! Więc nie musisz ich wdrażać samodzielnie. Sprawdź w Internecie, jak to działa w IDE!
@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
return item1.getId() == item2.getId();
}
SortedList
używa tej metody, aby sprawdzić, czy dwa elementy odnoszą się do tej samej rzeczy. W najprostszych słowach (bez wyjaśnienia, jak SortedList
Działa) służy do określenia, czy obiekt jest już zawarty w List
i czy należy odtworzyć animację dodaj, Przenieś lub zmień. Jeśli twoje modele mają identyfikator, Zwykle porównujesz tylko identyfikator w tej metodzie. Jeśli nie musisz wymyślić innego sposobu, aby to sprawdzić, ale jednak w końcu wdrożysz to zależy od konkretnej aplikacji. Zazwyczaj najprostszą opcją jest nadanie wszystkim modelom identyfikatora - który może być na przykład głównym polem klucza, jeśli odpytywanie danych z bazy danych.
Z SortedList.Callback
poprawnie zaimplementowanym możemy utworzyć instancję SortedList
:
final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);
Jako pierwszy parametr w konstruktorze SortedList
musisz przejść klasę swoich Modeli. Innym parametrem jest SortedList.Callback
zdefiniowany powyżej.
Adapter
z SortedList
powinno to wyglądać mniej więcej tak:
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
używane do sortowania elementu jest przekazywane in through the constructor, więc możemy użyć tego samego Adapter
, nawet jeśli elementy mają być wyświetlane w innej kolejności.
Już prawie skończyliśmy! Ale najpierw potrzebujemy sposobu, aby dodać lub usunąć elementy do Adapter
. W tym celu możemy dodać metody do Adapter
, które pozwalają nam dodawać i usuwać elementy do 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();
}
Nie musimy tutaj wywoływać żadnych metod powiadamiania, ponieważ SortedList
robi to już za pośrednictwem SortedList.Callback
! Poza tym realizacja metody te są dość proste z jednym wyjątkiem: metoda remove, która usuwa List
modeli. Ponieważ SortedList
ma tylko jedną metodę remove, która może usunąć pojedynczy obiekt, musimy zapętlić listę i usunąć modele jeden po drugim. Wywołanie beginBatchedUpdates()
na początku partii wszystkich zmian, które zamierzamy wprowadzić do SortedList
razem i poprawia wydajność. Kiedy wywołujemy endBatchedUpdates()
{[41] } jest powiadamiany o wszystkich zmianach na raz.
Dodatkowo to, co masz aby zrozumieć, że jeśli dodasz obiekt do SortedList
i jest już w SortedList
, nie zostanie on dodany ponownie. Zamiast tego SortedList
używa metody areContentsTheSame()
, aby dowiedzieć się, czy obiekt się zmienił - i czy posiada element w RecyclerView
zostanie zaktualizowany.
W każdym razie, Zwykle preferuję jedną metodę, która pozwala mi zastąpić wszystkie przedmioty w RecyclerView
naraz. Usuń wszystko, czego nie ma w List
i dodaj wszystkie elementy, których brakuje w 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();
}
To metoda ponownie grupuje wszystkie aktualizacje razem, aby zwiększyć wydajność. Pierwsza pętla jest odwrotna, ponieważ usunięcie elementu na początku spowoduje bałagan w indeksach wszystkich elementów, które pojawiają się po nim, a to może prowadzić w niektórych przypadkach do problemów, takich jak niespójności danych. Następnie dodajemy List
do SortedList
używając addAll()
, aby dodać wszystkie elementy, które nie są jeszcze w SortedList
i - tak jak opisałem powyżej-aktualizujemy wszystkie elementy, które są już w SortedList
, ale mają zmieniony.
I z tym Adapter
jest kompletna. Całość powinna wyglądać mniej więcej tak:
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();
}
}
Jedyne czego teraz brakuje to zaimplementowanie filtrowania!
Implementacja logiki filtra
Aby zaimplementować logikę filtra musimy najpierw zdefiniować List
wszystkich możliwych modeli. W tym przykładzie tworzę List
Z ExampleModel
instancji z tablicy filmów:
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);
}
Nic specjalnego się tu nie dzieje, po prostu Utwórz instancję Adapter
i ustaw ją na RecyclerView
. Następnie tworzymy List
modeli z nazw filmów w tablicy MOVIES
. Następnie dodajemy wszystkie modele do SortedList
.
Teraz możemy wrócić do onQueryTextChange()
, które zdefiniowaliśmy wcześniej i rozpocząć implementację logiki filtra:
@Override
public boolean onQueryTextChange(String query) {
final List<ExampleModel> filteredModelList = filter(mModels, query);
mAdapter.replaceAll(filteredModelList);
mBinding.recyclerView.scrollToPosition(0);
return true;
}
To znowu całkiem proste. Wywołujemy metodę filter()
i przekazujemy w List
z ExampleModel
s, jak również łańcuch zapytania. Następnie wywołujemy {[122] } na Adapter
i przekazujemy w filtrowanym List
zwrócone przez filter()
. Musimy również zadzwonić scrollToPosition(0)
na RecyclerView
, aby upewnić się, że użytkownik zawsze może zobaczyć wszystkie elementy podczas wyszukiwania czegoś. W przeciwnym razie RecyclerView
może pozostać w pozycji przewijanej w dół podczas filtrowania, a następnie ukryć kilka elementów. Przewijanie do góry zapewnia lepsze wrażenia użytkownika podczas wyszukiwania.
Jedyne, co pozostało do zrobienia, to zaimplementować filter()
siebie:
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;
}
Pierwszą rzeczą, którą tutaj robimy, jest wywołanie toLowerCase()
na łańcuchu zapytania. My nie chcemy, aby nasza funkcja wyszukiwania uwzględniała wielkość liter i wywołując toLowerCase()
na wszystkich porównywanych łańcuchach możemy zapewnić, że zwracamy te same wyniki niezależnie od wielkości liter. Następnie po prostu iteruje przez wszystkie modele w List
przekazaliśmy do niego i sprawdza, czy łańcuch zapytania jest zawarty w tekście modelu. Jeśli tak, to model jest dodawany do filtrowanego List
.
I to wszystko! Powyższy kod będzie działał na poziomie API 7 i wyższym, a zaczynając od poziomu API 11 otrzymasz element animacje za darmo!
Zdaję sobie sprawę, że jest to bardzo szczegółowy opis, który prawdopodobnie sprawia, że cała sprawa wydaje się bardziej skomplikowana niż w rzeczywistości jest, ale jest sposób, w jaki możemy uogólnić ten cały problem i uczynić implementację Adapter
opartą na {45]} znacznie prostszym.
Uogólnienie problemu i uproszczenie adaptera
W tym dziale Nie będę wdawać się zbytnio w szczegóły-częściowo dlatego, że biegnę z limitem znaków dla odpowiedzi na temat przepełnienia stosu, ale również dlatego, że większość z nich została już wyjaśniona powyżej - ale podsumowując zmiany: możemy zaimplementować klasę base Adapter
, która już zajmuje się radzeniem sobie z SortedList
, jak również wiążąc modele do ViewHolder
instancji i zapewnia wygodny sposób zaimplementowania Adapter
w oparciu o SortedList
. W tym celu musimy zrobić dwie rzeczy:
- musimy stworzyć interfejs
ViewModel
, który wszystkie klasy modelu muszą zaimplementować - musimy stworzyć
ViewHolder
podklasa definiująca metodębind()
, którejAdapter
może używać do automatycznego wiązania modeli.
Pozwala nam to po prostu skupić się na treści, która ma być wyświetlana w RecyclerView
poprzez implementację modeli i odpowiadających im implementacji {42]}. Używając tej klasy bazowej nie musimy się martwić o szczegóły Adapter
i jej SortedList
.
SortedListAdapter
Ze względu na limit znaków odpowiedzi na StackOverflow I nie można przejść przez każdy etap implementacji tej klasy bazowej ani nawet dodać pełnego kodu źródłowego tutaj, ale można znaleźć Pełny kod źródłowy tej klasy bazowej-nazwałem ją {149]} - w tym GitHub Gist.
Aby ułatwić Ci życie, opublikowałem bibliotekę na jCenter, która zawiera SortedListAdapter
! Jeśli chcesz go użyć, wszystko, co musisz zrobić, to dodać tę zależność do kompilacji aplikacji.plik:
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
Możesz znaleźć więcej informacji na temat ta biblioteka na stronie głównej biblioteki.
Korzystanie z SortedListAdapter
Aby skorzystać z SortedListAdapter
musimy wprowadzić dwie zmiany:
-
Zmień
ViewHolder
tak, aby rozszerzyłSortedListAdapter.ViewHolder
. Parametrem typu powinien być model, który powinien być związany z tymViewHolder
- w tym przypadkuExampleModel
. Musisz powiązać dane z modelami wperformBind()
zamiastbind()
.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); } }
-
Upewnij się, że wszystkie twoje modele wdrażają
ViewModel
interfejs:public class ExampleModel implements SortedListAdapter.ViewModel { ... }
Potem musimy tylko zaktualizować ExampleAdapter
, aby rozszerzyć SortedListAdapter
i usunąć wszystko, czego już nie potrzebujemy. Parametr type powinien być typem modelu, z którym pracujesz-w tym przypadku ExampleModel
. Ale jeśli pracujesz z różnymi typami modeli, ustaw parametr type na 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);
}
}
Potem już po nas! Jednak jeszcze jedna rzecz do wspomnienia: SortedListAdapter
nie ma tego samego add()
, remove()
lub replaceAll()
metody nasze oryginalne ExampleAdapter
miały. Używa oddzielnego obiektu Editor
do modyfikowania pozycji na liście, do których można uzyskać dostęp za pomocą metody edit()
. Jeśli chcesz usunąć lub dodać elementy, musisz wywołać edit()
, a następnie dodać i usunąć elementy na tej instancji Editor
, A gdy skończysz, wywołaj commit()
na nim, aby zastosować zmiany do SortedList
:
mAdapter.edit()
.remove(modelToRemove)
.add(listOfModelsToAdd)
.commit();
Wszystkie wprowadzone w ten sposób zmiany są zestawiane razem, aby zwiększyć wydajność. Metodę replaceAll()
zaimplementowaliśmy w rozdziały powyżej są również obecne na tym Editor
obiekcie:
mAdapter.edit()
.replaceAll(mModels)
.commit();
Jeśli zapomnisz zadzwonić commit()
wtedy żadna z Twoich zmian nie zostanie zastosowana!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-01 19:48:13
Wystarczy dodać filter
metodę w 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
jest inicjalizowana w konstruktorze adaptera jak itemsCopy.addAll(items)
.
Jeśli to zrobisz, po prostu zadzwoń filter
z 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;
}
});
To przykład z filtrowania mojej książki telefonicznej po imieniu i numerze telefonu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-15 22:17:49
Podążając za @ Shruthi Kamoji w czystszy sposób, możemy po prostu użyć filtra, do tego jest przeznaczony:
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 jest typem generycznym, możesz go rozszerzyć używając swojej klasy:
public class customerAdapter extends GenericRecycleAdapter<CustomerModel>
Lub po prostu zmień E na typ, który chcesz (<CustomerModel>
na przykład)
Następnie z searchView (widget można umieścić w 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;
}
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-21 11:25:05
Po prostu utwórz dwie listy w adapterze jedna orignal i jedna temp i implementuje Filtrowalną .
@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();
}
};
}
Gdzie
public GenericBaseAdapter(Context mContext, List<T> itemList) {
this.mContext = mContext;
this.itemList = itemList;
this.origList = itemList;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-18 11:10:42
Polecam zmodyfikować rozwiązanie @ Xaver Kapeller z 2 rzeczy poniżej, aby uniknąć problemu po wyczyszczeniu przeszukiwanego tekstu (filtr nie działa już) ze względu na liście tył adaptera ma mniejszy rozmiar niż lista filtrów i indexoutofboundsexception stało. Więc kod musi zmodyfikować jak poniżej
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);
}
}
I modyfikować również w funkcjonalności 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);
}
}
Mam nadzieję, że to może Ci pomóc!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-10 21:42:08
Rozwiązałem ten sam problem używając linku z pewnymi modyfikacjami w nim. Filtr wyszukiwania w RecyclerView z kartami. Czy to w ogóle możliwe? (mam nadzieję, że to pomoże).
Oto moja klasa adaptera
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);
}
}
//Klasa filtra
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();
}
}
//Klasa aktywności
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;
}
}
W metodzie OnQueryTextChangeListener() użyj swojego adaptera. Rzuciłem go na fragment, ponieważ mój adpter jest we fragmentach. Możesz użyć adaptera bezpośrednio, jeśli jest w klasie aktywności.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-09 12:41:51
W Adapterze:
public void setFilter(List<Channel> newList){
mChannels = new ArrayList<>();
mChannels.addAll(newList);
notifyDataSetChanged();
}
W Aktywności:
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;
}
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-30 15:06:13