android:如何从 RecyclerView 中的搜索中修复 SpannableStrings?

android: how to fix SpannableStrings from search in RecyclerView?

我在 MainActivity 的顶部工具栏上设置了一个 SearchView。用户单击搜索图标,然后从软键盘输入搜索字符。 ViewModel 观察器 returns 从 Room 数据库匹配数据,然后为 CardsAdapter 运行 setFilter() 方法以设置 RecyclerView 列表,显示与搜索字符串匹配的 CardView。我在适配器的 onBindViewHolder() 中设置了 Spannable 代码,以针对每个返回的 CardView 突出显示与搜索字符串匹配的字符。

搜索代码返回正确的 CardView。但是,Spannable 代码未按预期工作。我在这里错过了什么?

以下是我要修复的结果:

  1. 如果在 SearchView 中输入小“t”,

    a) 然后只有第一个“t”被突出显示(绿色)。所有剩余的小“t” CardView EditText 行未突出显示。我希望突出显示所有小“t”。

    b) none 具有大写“T”的 CardView 以绿色突出显示。我想要 所有大写字母“T”也将突出显示。

这是一个示例 CardView,显示了这两个问题:只有第一个小写的“t”以绿色突出显示,而大写的“T”根本没有突出显示。

  1. 如果在 SearchView 中输入大写“T”,

    a) 然后只有第一个“T”突出显示(绿色)。所有剩余的大写“T”都不是 突出显示。我希望突出显示所有“T”。

    b) none 具有小“t”的 CardView 以绿色突出显示。我愿意所有 小“t”也被突出显示。

在 onBindViewHolder() 中,如果我将“String todoHighlight = card.getTodo()”附加到“.toLowerCase()”,则:

  1. 如果在 SearchView 中输入小“t”,

    a) 然后只有第一个“T”或“t”突出显示(绿色)。所有剩余的“T”或“t”是 没有突出显示。我希望它们都突出显示。

    b) 大写字母“T”如我所愿以绿色突出显示。

  2. 如果在 SearchView 中输入大写字母“T”,

    a) none 具有小“t”或大写“T”的 CardView 以绿色突出显示。我希望它们都突出显示。

    主要活动

    ...

     mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
             EditText mSearchEditText = mSearchView.findViewById(androidx.appcompat.R.id.search_src_text);
             mSearchEditText.setInputType(android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
             // use the link to EditText of the SearchView so that a
             // TextWatcher can be used.
             mSearchEditText.addTextChangedListener(new TextWatcher() {
    
         @Override
         public void afterTextChanged(Editable s) {
    
             if (s.toString().length() > 0) {
    
                 String queryText = "%" + s.toString() + "%";
    
                 mQuickcardViewModel.searchQuery(queryText).observe(MainActivity.this, searchQuickcards -> {
    
                     searchList = searchQuickcards;
                     if (!mSearchView.isIconified() && searchList.size() > 0) {
                         cardsAdapter.setFilter(searchList, s.toString());
                     }
                 });    
    

    卡片适配器

    ...

     public void setFilter(List<card> searchCards, String searchString) {
         mListItems = new ArrayList<>();
         mListItems.clear();
         mListItems.addAll(searchCards);
         this.searchString = searchString;
         notifyDataSetChanged();
     }
    
     public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
    
         ...
         // also tried "String todoHighlight = card.getTodo().toLowerCase();"
         String todoHighlight = card.getTodo();
    
         if (searchString != null && !searchString.isEmpty() && todoHighlight.contains(searchString)) {
    
             int todoStartPos = todoHighlight.indexOf(searchString);
             int todoEndPos = todoStartPos + searchString.length();
             Spannable spanString2 = new SpannableString(itemHolder.cardBlankText2.getText());
    
             if (spanString2 != null) {
                 spanString2.removeSpan(itemHolder.getForegroundColorSpan());
                 itemHolder.cardBlankTextNumstotal.setText(spanString2);
                 spanString2.setSpan(new ForegroundColorSpan(Color.GREEN), todoStartPos, todoEndPos,
                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                 itemHolder.cardBlankText2.setText(spanString2);
             }
         }
    

修改后的代码有时能正常工作,有时会崩溃:

in onBindViewHolder():

String todoSearchHighlight = mListItems.get(position).getTodo();
Spannable spannable = new SpannableString(todoSearchHighlight);
if (searchString != null && !TextUtils.isEmpty(searchString)) {
    highLightText(spannable, todoSearchHighlight, 0);
    itemHolder.cardBlankText2.setText(spannable);
}
else { // searchString == null
    if (spannable != null) {
    spannable.removeSpan(itemHolder.getForegroundColorSpan());
    itemHolder.cardBlankText2.setText(todoSearchHighlight);
    }
}

private void highLightText(Spannable spannable, String string, int start) {

    int startPos = start + string.substring(start).toLowerCase(Locale.US).indexOf(searchString.toLowerCase(Locale.US));
    int endPos = startPos + searchString.length();
    if (string.substring(endPos).toLowerCase(Locale.US).contains(searchString.toLowerCase(Locale.US))){
        highLightText(spannable, string, endPos);
    }
    ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.GREEN);
    spannable.setSpan(foregroundColorSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
     

logcat 执行以下答案后出错:

java.lang.IndexOutOfBoundsException: setSpan(-1 ... 0) 在0之前开始 在 android.text.SpannableStringInternal.checkRange(SpannableStringInternal.java:442) 在 android.text.SpannableStringInternal.setSpan(SpannableStringInternal.java:163) 在 android.text.SpannableStringInternal.setSpan(SpannableStringInternal.java:152) 在 android.text.SpannableString.setSpan(SpannableString.java:46) 在 com.todo.quickcards.adapter.CardsAdapter.highLightText(CardsAdapter.java:665) 在 com.todo.quickcards.adapter.CardsAdapter.onBindViewHolder(CardsAdapter.java:643)

以下代码用于突出显示问题中的多个文本:

@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
    if (!filterPattern.equals("")) {
        String tmpCname = data.get(position);

        //int startPos = tmpCname.toLowerCase(Locale.US).indexOf(filterPattern.toLowerCase(Locale.US));
        //int endPos = startPos + filterPattern.length();
        //Spannable spannable = new SpannableString(tmpCname);
        //ColorStateList blueColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.BLUE});
        //TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, blueColor, null);
        //spannable.setSpan(highlightSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

        Spannable spannable = new SpannableString(tmpCname);
        highLightText(spannable, tmpCname, 0);

        holder.mTitle.setText(spannable);
    } else {
        holder.mTitle.setText(data.get(position));
    }
}

private void highLightText(Spannable spannable, String string, int start) {
    int startPos = start + string.substring(start).toLowerCase(Locale.US).indexOf(filterPattern.toLowerCase(Locale.US));
    if (startPos > -1) {
        int endPos = startPos + filterPattern.length();
        if (string.substring(endPos).toLowerCase(Locale.US).contains(filterPattern.toLowerCase(Locale.US))){
            highLightText(spannable, string, endPos);
        }
        ColorStateList blueColor = new ColorStateList(new int[][]{new int[]{}}, new int[]{Color.BLUE});
        TextAppearanceSpan highlightSpan = new TextAppearanceSpan(null, Typeface.BOLD, -1, blueColor, null);
        spannable.setSpan(highlightSpan, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
}