Как мне разрешить поиск в Android, который работает с акцентами персонажа? - PullRequest
3 голосов
/ 26 октября 2019

Я реализовал механизм поиска в моем приложении, так что когда я ищу имя или адрес электронной почты, он отображает строку с соответствующим символом. Тем не менее, в моем списке есть несколько акцентированных строк, и когда я ищу с обычным символом, относящимся к этому определенному акценту, скажем, если у меня есть строка «àngela», и я ищу «angela», она не отображает строку, если я не ищу с точной строкой »Анджела».

Я пытаюсь заставить его работать независимо от акцента или нет, скажем, если я наберу «а», он должен показать все строки, содержащие «а» и «а», и наоборот. Есть идеи, как это сделать? Я просмотрел кучу статей в Интернете, например: Как игнорировать ударение в запросе SQLite (Android)"и пробовал нормализатор тоже, но он частично работает, если я ищу" a ", он показывает акцентированныйбуквы с обычными буквами тоже, но если я ищу с акцентированными буквами, он ничего не показывает.

Вот мой код для фильтра:

 @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence charSequence) {
                String charString = charSequence.toString();
                if (charString.isEmpty()) {
                    mSearchGuestListResponseListFiltered = mSearchGuestListResponseList;
                } else {
                    List<RegisterGuestList.Guest> filteredList = new ArrayList<>();
                    for (RegisterGuestList.Guest row : mSearchGuestListResponseList) {

                        // name match condition. this might differ depending on your requirement
                        // here we are looking for name or phone number match
                        String firstName = row.getGuestFirstName().toLowerCase();
                        String lastName = row.getGuestLastName().toLowerCase();
                        String name = firstName + " " +lastName;
                        String email = row.getGuestEmail().toLowerCase();
                        if ( name.trim().contains(charString.toLowerCase().trim()) ||
                                email.trim().contains(charString.toLowerCase().trim())){
                            filteredList.add(row);
                            searchText = charString.toLowerCase();
                        }
                    }

                    mSearchGuestListResponseListFiltered = filteredList;
                }

                FilterResults filterResults = new FilterResults();
                filterResults.values = mSearchGuestListResponseListFiltered;
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
                mSearchGuestListResponseListFiltered = (ArrayList<RegisterGuestList.Guest>) filterResults.values;
                notifyDataSetChanged();
            }
        };
    }

Вот весь класс адаптера, если кому-то интересно: https://pastebin.com/VxsWWMiS Вот соответствующий вид деятельности:

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                mSearchGuestListAdapter.getFilter().filter(query);

                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                mSearchGuestListAdapter.getFilter().filter(newText);
                mSearchGuestListAdapter.notifyDataSetChanged();
                mSearchGuestListAdapter.setFilter(newText);

                if(mSearchGuestListAdapter.getItemCount() == 0){


                    String sourceString = "No match found for <b>" + newText + "</b> ";
                    mNoMatchTextView.setText(Html.fromHtml(sourceString));
                } else {
                    mEmptyRelativeLayout.setVisibility(View.GONE);
                    mRecyclerView.setVisibility(View.VISIBLE);
                }
                return false;
            }
        });

Рад поделиться любой информацией, если это необходимо. Кроме того, случайно я получаю метод indexoutofboundexception onBind () при поиске (используйте для просмотра список просмотрщиков):

java.lang.IndexOutOfBoundsException: Index: 7, Size: 0
        at java.util.ArrayList.get(ArrayList.java:437)

Есть идеи, как это сделать?

1 Ответ

1 голос
/ 26 октября 2019

В общем, я бы рекомендовал использовать Collator с настройкой силы Collator.PRIMARY для сравнения строк, содержащих акценты и различные варианты (например, N против n и é против e). К сожалению, Collator не имеет функции contains().

Таким образом, мы сделаем нашу собственную.

private static boolean contains(String source, String target) {
    if (target.length() > source.length()) {
        return false;
    }

    Collator collator = Collator.getInstance();
    collator.setStrength(Collator.PRIMARY);

    int end = source.length() - target.length() + 1;

    for (int i = 0; i < end; i++) {
        String sourceSubstring = source.substring(i, i + target.length());

        if (collator.compare(sourceSubstring, target) == 0) {
            return true;
        }
    }

    return false;
}

Это перебирает исходную строку и проверяет, каждая ли подстрока сДлина, соответствующая цели поиска, равна цели поиска в том, что касается Collator.

Например, давайте представим, что нашей исходной строкой является "This is a Tèst", и мы ищем слово "test". Этот метод будет перебирать каждую подстроку из четырех букв:

This
his 
is i
s is
 is 
is a
s a 
 a T
a Tè
 Tès
Tèst

и вернет true, как только найдет совпадение. Так как сила установлена ​​на Collator.PRIMARY, сборщик считает, что "Tèst" и "test" равны, и поэтому наш метод возвращает true.

Вполне возможно, что нужно сделать больше оптимизацийк этому методу, но это должно быть разумной отправной точкой.

Редактировать : Одна из возможных оптимизаций заключается в использовании ключей сопоставления, а также известных деталей реализации RuleBasedCollator и RuleBasedCollationKey (при условии, что у вас есть Google Guava в вашем проекте):

private static boolean containsBytes(String source, String target) {
    Collator collator = Collator.getInstance();
    collator.setStrength(Collator.PRIMARY);

    byte[] sourceBytes = dropLastFour(collator.getCollationKey(source).toByteArray());
    byte[] targetBytes = dropLastFour(collator.getCollationKey(target).toByteArray());

    return Bytes.indexOf(sourceBytes, targetBytes) >= 0;
}

private static byte[] dropLastFour(byte[] in) {
    return Arrays.copyOf(in, in.length - 4);
}

Это значительно более хрупко (вероятно, не работает для всех языков), но в моих тестах это примерно в 2-10 раз быстрее.

Редактировать : для поддержки выделения вы должны преобразовать contains() в indexOf(), а затем использовать эту информацию:

private static int indexOf(String source, String target) {
    if (target.length() > source.length()) {
        return -1;
    }

    Collator collator = Collator.getInstance();
    collator.setStrength(Collator.PRIMARY);

    int end = source.length() - target.length() + 1;

    for (int i = 0; i < end; i++) {
        String sourceSubstring = source.substring(i, i + target.length());

        if (collator.compare(sourceSubstring, target) == 0) {
            return i;
        }
    }

    return -1;
}

И затем вы можете применить еекак это:

String guestWholeName = guest.getGuestFirstName() + " " + guest.getGuestLastName();
int wholeNameIndex = indexOf(guestWholeName, searchText);

if (wholeNameIndex > -1) {
    Timber.d("guest name first : guest.getGuestFirstName() %s", guest.getGuestFirstName());
    Timber.d("guest name last : guest.getGuestLastName() %s", guest.getGuestLastName());

    int endPos = wholeNameIndex + searchText.length();

    Spannable spannable = new SpannableString(guestWholeName);
    Typeface firstNameFont = Typeface.createFromAsset(context.getAssets(), "fonts/Graphik-Semibold.otf");
    spannable.setSpan(new CustomTypefaceSpan("", firstNameFont), wholeNameIndex, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    Objects.requireNonNull(guestName).setText(spannable);
} else {
    Objects.requireNonNull(guestName).setText(guestWholeName);
}
...