【问题标题】:How do I allow search in android that works with character accents aswell?如何允许在 android 中搜索也可以使用字符重音?
【发布时间】:2019-10-25 21:06:46
【问题描述】:

我在我的应用程序中实现了一种搜索机制,这样当我搜索姓名或电子邮件时,它会显示带有匹配字符的字符串。但是,我的列表中有一些重音字符串,当我使用与该特定重音相关的常规字符进行搜索时,假设我有字符串“àngela”并且我搜索“angela”,除非我使用确切的字符串搜索,否则它不会显示字符串“安吉拉”。

无论口音与否,我都试图让它工作,比如如果我输入 "à" ,它应该显示所有包含 "à" 和 "a" 的字符串,反之亦然。知道该怎么做吗?我在网上查了一堆文章,例如:How to ignore accent in SQLite query (Android)" 并尝试了 normalizer,但它部分有效,如果我搜索“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() 方法(使用 recyclerview 作为列表):

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

知道该怎么做吗?

【问题讨论】:

    标签: java android android-layout android-recyclerview android-search


    【解决方案1】:

    一般来说,我建议使用强度设置为Collator.PRIMARYCollator 来比较包含重音和不同大小写的字符串(例如,Nné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;
    }
    

    这会遍历源字符串,并检查每个与搜索目标长度相同的子字符串是否等于搜索目标,就 Collat​​or 而言。

    例如,假设我们的源字符串是"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

    很有可能对这种方法进行更多优化,但这应该是一个合理的起点。

    编辑:一种可能的优化是利用排序规则以及RuleBasedCollatorRuleBasedCollationKey 实现的已知细节(假设您的项目中有 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);
    }
    

    【讨论】:

    • 这似乎确实有效,但是,我经常遇到这种崩溃:java.lang.IndexOutOfBoundsException: Index: 7, Size: 0 at java.util.ArrayList.get(ArrayList.java:437)在 xxx.searchGuests.SearchGuestListAdapter$ViewHolder.onBind(SearchGuestListAdapter.java:196) ?我已经在 pastebin 链接中发布了我的 searchadapter 代码。也许我的逻辑有问题?
    • 这也是我在更新后收到的另一个常见崩溃报告:pastebin.com/tN1gciZ7
    • 我很乐意看看(我相信其他人也会这样做),但我建议您提出一个新问题,并详细说明。
    • 不确定对搜索过滤器所做的更改是否会触发此问题。我可以很快提出一个新问题,但想知道它是否与将 notifydatasetchanged 添加到其中一个函数一样快?或更新过滤结果中的一行。
    • 另外,关于这个问题,我如何使用你的功能来突出我的搜索结果?重音结果不会突出显示,但只有普通字母会突出显示。我的 onbind 函数(应用高亮的地方)需要更改哪些内容才能应用高亮?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-15
    • 2011-07-08
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    • 2010-11-25
    • 1970-01-01
    相关资源
    最近更新 更多