Вопрос или проблема
У меня есть SearchView внутри Toolbar, который располагается над списком CardView в RecyclerView. SearchView корректно работает для фильтрации списка на основе текстовых вводов. Однако код для выделения искомого текста цветом Color.GREEN не работает должным образом.
Первый CardView в вертикальном списке RecyclerView никогда не имеет выделенного искомого текста. А остальные CardView с искомым текстом ниже него также часто не выделяются. Кроме того, при прокрутке вниз и обратно вверх по списку некоторые найденные искомые тексты начинают выделяться, хотя этот текст не был выделен, когда список RecyclerView был первоначально возвращен.
Возможно, проблема связана с использованием ListAdapter в качестве адаптера, так как представления обновляются в фоновом потоке, и набор данных еще не готов? Есть идеи, как правильно выделить искомый текст, используя ListAdapter в вертикальном прокручиваемом RecyclerView?
MainActivity
....
public boolean onCreateOptionsMenu(Menu menu) {
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
filter(newText);
return false;
}
});
}
private void filter(String searchText) {
ArrayList<Card> searchList = new ArrayList<>();
for (Card cardItem : mCards) {
if (cardItem.getTodo().toLowerCase().contains(searchText.toLowerCase(Locale.US))) {
searchList.add(cardItem);
}
}
if (!searchList.isEmpty()) {
adapter.setFilter(searchList, searchText);
}
}
ListAdapter
public class CardRVAdapter extends ListAdapter<Card, CardRVAdapter.ViewHolder> {
....
private String searchString = "";
public Spannable spannable;
public void setFilter(List<Card> newSearchList, String adapSearchText) {
if (newSearchList != null && !newSearchList.isEmpty()) {
this.searchString = adapSearchText.toLowerCase(Locale.US);
ArrayList<Card> tempList = new ArrayList<>(newSearchList);
submitList(tempList);
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView cardBlankText2; // отображает текст в CardView и сопоставляется с введенным искомым текстом.
ForegroundColorSpan fcs = new ForegroundColorSpan(Color.GREEN);
public ForegroundColorSpan getFCS() {
return fcs;
}
}
public ViewHolder(@NonNull final View itemView) {
super(itemView);
cardBlankText2 = itemView.findViewById(R.id.cardBlankText2);
}
void bindData(Card card, final int position) {
spannable = Spannable.Factory.getInstance().newSpannable(cardBlankText2.getText().toString());
// Получить любые предыдущие спаны и удалить их
ForegroundColorSpan[] foregroundSpans = spannable.getSpans(0,spannable.length(), ForegroundColorSpan.class);
// Подсветить совпадения из искомых символов зеленым цветом.
if (searchString != null && !TextUtils.isEmpty(searchString)) {
for (ForegroundColorSpan span : foregroundSpans) {
spannable.removeSpan(span);
}
int index = spannable.toString().toLowerCase(Locale.US).indexOf(searchString);
while (index != -1) {
spannable.setSpan(getFCS(), index, index + searchString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
index = spannable.toString().indexOf(searchString,index + searchString.length());
}
cardBlankText2.setText(spannable, TextView.BufferType.SPANNABLE);
}
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
final Card card = getCardAt(position);
if (card != null) {
holder.bindData(card, position);
}
}
public Card getCardAt(int position) {
return getItem(position);
}
}
Ответ или решение
Для решения проблемы с подсветкой текста в RecyclerView
, когда используется ListAdapter
, необходимо внести несколько изменений в ваш код. Основные проблемы заключаются в обновлении Spannable
текста и в том, как ListAdapter
управляет повторной привязкой элементов, когда список обновляется. Вот исправленный и улучшенный вариант вашего кода:
Внесенные исправления
-
Использование
Spannable
: Лучше создавать новый объектSpannable
в методеbindData
, чтобы не использовать один и тот же объект для всех элементов. Это гарантирует, что каждый элемент получит уникальную подсветку. -
Удаление предыдущих спанов: Нужно удалить все спаны, а не только те, которые были установлены в последнем обновлении списка.
-
Оптимизация кода: Сделаем код более читабельным и надежным.
Обновленный код
public class CardRVAdapter extends ListAdapter<Card, CardRVAdapter.ViewHolder> {
private String searchString = "";
public void setFilter(List<Card> newSearchList, String adapSearchText) {
this.searchString = adapSearchText.toLowerCase(Locale.US);
ArrayList<Card> tempList = new ArrayList<>(newSearchList);
submitList(tempList);
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView cardBlankText2;
public ViewHolder(@NonNull View itemView) {
super(itemView);
cardBlankText2 = itemView.findViewById(R.id.cardBlankText2);
}
void bindData(Card card) {
String cardText = card.getTodo();
Spannable spannable = new SpannableString(cardText);
if (!TextUtils.isEmpty(searchString)) {
int index = cardText.toLowerCase(Locale.US).indexOf(searchString);
while (index != -1) {
spannable.setSpan(new ForegroundColorSpan(Color.GREEN), index, index + searchString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
index = cardText.toLowerCase(Locale.US).indexOf(searchString, index + searchString.length());
}
}
cardBlankText2.setText(spannable, TextView.BufferType.SPANNABLE);
}
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Card card = getItem(position);
holder.bindData(card);
}
}
Пояснения к изменениям
-
Создание нового
Spannable
: Теперь для каждого элемента списка создается новый объектSpannable
, что избавляет от проблем с повторным использованиемSpannable
между элементами. -
Удаление спанов: Удаление всех старых спанов больше не требуется, так как каждый элемент получает свою новую строку
Spannable
. -
Улучшение читаемости: Код стал более понятным, и каждая часть кода имеет ясное назначение.
Заключение
С данным исправленным кодом ваша подсветка текста должна корректно работать для всех элементов в RecyclerView
. Каждый элемент будет показывать выделение искомого текста при прокрутке, и не будет возникать проблем с неправильным отображением подсветки. Попробуйте этот подход и убедитесь, что он решает вашу основную проблему.