Recycler View Performance when using SpannedString

458 Views Asked by At

I'm using SpannedString for coloring some specials character in textView. When I'm not using SpannedString and just set plain text, everything works fine, But when I use SpannedString, scrolling is very slow and is very laggy.

I know that onBindViewHolder function should be very simple but I tried different ways to store spanned String and also used htmlString, but got no improvements. I also use regex to find special characters, but in LG devices, the previous character is colored too and I have to force other characters' color to be black.

I also test setHasFixedSize, setExtraLayoutSpace, and constraint-layout; but it doesn't change.

Here is my onBindViewHolder function:

@Override
public void onBindViewHolder(AyeViewHolder holder, int i) {

    holder.setIsRecyclable(false);

    SpannedString result = new SpannedString("");
    for (int j = 0; j < ayeList.get(i).getAye().length(); j++) {
        SpannableStringBuilder wordtoSpan = new SpannableStringBuilder(ayeList.get(i).getAye().substring(j,j+1));

        if (arabicV.contains(ayeList.get(i).getAye().substring(j, j + 1))) {
            wordtoSpan.setSpan(new ForegroundColorSpan(Color.parseColor(PrefUtils.getFromPrefs(context,
                    PrefUtils.ARABIC_COLOR, "#FF0000"))),
                    0, 1 , 0);

        }else if (endSuffix.contains(ayeList.get(i).getAye().substring(j, j + 1))) {
            wordtoSpan.setSpan(new ForegroundColorSpan(Color.parseColor( "#00acc2")),
                    0, 1 , 0);
        }else {
            wordtoSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#000000")),
                    0, 1, 0);
        }
        result = (SpannedString) TextUtils.concat(result,"",wordtoSpan);
    }
    holder.aye.setText(result, TextView.BufferType.SPANNABLE);
}

How can I fix this?

Thanks

here is a screenshot from my recylcerview

3

There are 3 best solutions below

0
SergeyBukarev On

You need to out a loop with the search for symbols for highlighting from the onBindViewHolder method and transfer it to the model. You need to calculate all the intervals in the some place - before output in adater. And then in the method onBindViewHolder just use them.

In your model

public class Aye {
    private String aye;
    private ArrayList<SpannableStringBuilder> spans;

    public Aye(String aye, String arabicV, String endSuffix) {
        this.aye = aye;
        this.spans = getSearchSpans(aye, arabicV, endSuffix);
    }

    public String getAye() {
        return aye;
    }

    public ArrayList<SpannableStringBuilder> getSpans() {
        return spans;
    }

    private ArrayList<SpannableStringBuilder> getSearchSpans(String aye, String arabicV, String endSuffix) {
        ArrayList<SpannableStringBuilder> spans = new ArrayList<>();
        for (int j = 0; j < aye.length(); j++) {
            SpannableStringBuilder wordToSpan = new SpannableStringBuilder(aye.substring(j, j + 1));

            if (arabicV.contains(aye.substring(j, j + 1))) {
                wordToSpan.setSpan(new ForegroundColorSpan(Color.parseColor(PrefUtils.getFromPrefs(context, PrefUtils.ARABIC_COLOR, "#FF0000"))), 0, 1, 0);

            } else if (endSuffix.contains(aye.substring(j, j + 1))) {
                wordToSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#00acc2")), 0, 1, 0);
            } else {
                wordToSpan.setSpan(new ForegroundColorSpan(Color.parseColor("#000000")), 0, 1, 0);
            }

            SpannedString result = (SpannedString) TextUtils.concat(result, "", wordToSpan);
            spans.add(wordToSpan);
        }
        return spans;
    }
}

in your adapter

@Override
public void onBindViewHolder(AyeViewHolder holder, int i) {

    String aye = ayeList.get(i).getAye();
    ArrayList<SpannableStringBuilder> ayeSpans = ayeList.get(i). getSpans();

    for (SpannableStringBuilder span : ayeSpans) {
        holder.aye.setText(span, TextView.BufferType.SPANNABLE);
    }
}
0
AudioBubble On

You must relax the loop by taking out as many statements, assignments as you can,
like before the loop declare these:

    ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.parseColor(PrefUtils.getFromPrefs(context, PrefUtils.ARABIC_COLOR, "#FF0000")));
    ForegroundColorSpan colorSpanEnd = new ForegroundColorSpan(Color.parseColor( "#00acc2"));
    ForegroundColorSpan colorSpan0 = new ForegroundColorSpan(Color.parseColor( "#000000"));

and inside the loop:

String sub = ayeList.get(i).getAye().substring(j,j+1);

and then:

        if (arabicV.contains(sub)) {
            wordtoSpan.setSpan(colorSpan,0, 1 , 0);

        }else if (endSuffix.contains(sub)) {
            wordtoSpan.setSpan(colorSpanEnd,0, 1 , 0);
        }else {
            wordtoSpan.setSpan(colorSpan0,0, 1, 0);
        }
0
YunusEmre On
holder.setIsRecyclable(false);

You may be experiencing performance issues because of this line because each item in the RecyclerView will create a new ViewHolder instead of recycling existing ones. You can remove this line after following above answers.