5

Why links in ListView are lost, when scrolling? From debugging it's clear, that spans are not added second time on a TextView from the convertView.

Here's a piece of code which is called from adapter's getView.

    ...
    String body = MyItemDetails.getBody(); // String to linkify

    final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
    viewHolder.textView.setText(spannable);

    viewHolder.textView.setTextIsSelectable(true); // adds additional spans
    viewHolder.textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
    viewHolder.textView.setAutoLinkMask(Linkify.WEB_URLS);
    ...

MyCustomUri.addHashtagSpans() creates a SpannableString with MyCustomSpan with extends URLSpan.

Problem is that when I scroll up and down in the ListView links are lost. Whereas when screen is opened 1st time it's set correctly.

Now I made a dirty fix by disabling reuse of convertView :( Any ideas how to solve this problem better?

ViliusK
  • 11,345
  • 4
  • 67
  • 71

4 Answers4

1

Some of the spannable information is likely being lost when the textview's data is written to a parcel for retention.

See TextView.onSaveInstanceState(), TextView.onRestoreInstanceState(), and TextView.SavedState.

It can often be very frustrating to determine what android will and will not retain. I often just setSaveEnabled(false) on my views to disable the unpredictable default behaviours of the base widgets.

Also, the viewholder pattern is only really intended for retaining view/layout instance hierarchies. To save you from having to inflate or find your views every getView(). It's always your responsibility to update a view's data when presenting it from getView().

You don't need to completely disable the viewholder pattern, instead just simply update the text every getView(), as you may already be doing.

Kyle Ivey
  • 5,992
  • 1
  • 23
  • 35
  • Tried `setSaveEnabled(false)`, but did not help :( – ViliusK Jan 24 '14 at 08:33
  • Ya that's just an aside. It's not going to solve the problem directly – Kyle Ivey Jan 24 '14 at 17:31
  • The point is that you cannot always rely on android to retain your view state and should be in the habit of doing it yourself. – Kyle Ivey Jan 24 '14 at 17:35
  • So is there a way to reset `TextView`'s state? – ViliusK Jan 24 '14 at 17:53
  • sure, `TextView.setText()`. From the [SavedState](https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L8871) class we can see that this is pretty much all that's retained. – Kyle Ivey Jan 24 '14 at 19:24
  • The persistence of data through configuration changes using SavedState and view recycling are two very different mechanisms. None of this applies to how `ListView` reuses view elements in `getView()`. – devunwired Jan 26 '14 at 05:39
1

Hello Use this custom class

public class MyCustomSpannable extends ClickableSpan {
String Url;
Context mContext;

public MyCustomSpannable(String Url, Context context) {
    this.Url = Url;
    mContext = context;
}

@Override
public void updateDrawState(TextPaint ds) {
    // Customize your Text Look if required
    ds.setColor(mContext.getResources().getColor(R.color.red_text));
    ds.setFakeBoldText(true);
    // ds.setStrikeThruText(true);
    ds.setTypeface(CommonFunctios.getfontNormal(mContext));
    // ds.setUnderlineText(true);
    // ds.setShadowLayer(10, 1, 1, Color.WHITE);
    // ds.setTextSize(15);
}

@Override
public void onClick(View widget) {
}

public String getUrl() {
    return Url;
}
}

and in adapter replace your code with this

String text = holder.txt_terms.getText().toString();
SpannableStringBuilder stringBuilder = new SpannableStringBuilder(text);
MyCustomSpannable customSpannable = new MyCustomSpannable(text,
            mcontext) {

        @Override
        public void onClick(View widget) {
            Log.e("on click", "message");
            ((OpticalOffersActivity) mcontext).callDialogBox(position);
        }
    };
    stringBuilder.setSpan(customSpannable, 0, text.length(),
            Spannable.SPAN_INCLUSIVE_INCLUSIVE);

    holder.txt_terms.setText(stringBuilder, BufferType.SPANNABLE.SPANNABLE);
    holder.txt_terms.setMovementMethod(LinkMovementMethod.getInstance());

Hope it will help you.

BSKANIA
  • 1,317
  • 15
  • 27
1
if(convertView==null)
    {

        convertView.setTag(holder);
    }
else
    {
            holder = (ViewHolder)convertView.getTag();
    }




 ...
        String body = MyItemDetails.getBody(); // String to linkify

        final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
        viewHolder.textView.setText(spannable);

        viewHolder.textView.setTextIsSelectable(true); // adds additional spans
        viewHolder.textView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
        viewHolder.textView.setAutoLinkMask(Linkify.WEB_URLS);
        ...

That spannable code must be placed outside the if-else loop in the getView() method, like the way I did it in the above code.

Avi Singh
  • 111
  • 6
  • That's true, I've placed spannable code outside if-else block. Issue comes when convertView is reused. `MyCustomUri` spans are not added second time. – ViliusK Jan 24 '14 at 08:35
1

There are a couple problems at play here, so let me address them one at a time. The issues you've asked about directly (links disappearing) is a side effect of the fact that the auto linking behavior in TextView doesn't necessarily work that well when you are also adding your own spans to the text manually...best not to use it. Remove the setAutoLinkMask() trigger and the disappearing links issue will go away.

Instead, you can easily add the same web linking behavior directly into your text span with Linkify. However, this is only part of your problem. The MovementMethod you have chosen isn't really compatible with clickable links. The reason it (partially) works in your code now is because the auto link mask is causing the MovementMethod of the view to be secretly massaged under the hood to a LinkMovementMethod...which then gets reset after the view is recycled. A pattern I typically use (applied to your code example) would be:

final Spannable spannable = MyCustomUri.addHashtagSpans(context, body);
Linkify.addLinks(spannable, Linkify.WEB_URLS);
viewHolder.textView.setText(spannable);

addLinkMovementMethod(textView);

Where addLinkMovementMethod() is a helper I have that looks like this:

private void addLinkMovementMethod(TextView t) {
    MovementMethod m = t.getMovementMethod();

    if ((m == null) || !(m instanceof LinkMovementMethod)) {
        if (t.getLinksClickable()) {
            t.setMovementMethod(LinkMovementMethod.getInstance());
        }
    }
}

This simply keeps from resetting the value on each view recycle if it isn't necessary. The previous code block will give you links that click properly and never disappear...

However, I'm guessing from the methods you've called that you are also attempting to make the linked text in the list selectable (e.g. calling setTextIsSelectable() and choosing the ArrowKeyMovementMethod). This gets a little trickier because of the MovementMethod issue I discussed above. In order to create a MovementMethod that supports both link clicks and text selection, I'll direct you to this existing SO post on the subject which includes sample code on the customizations you need to make: Can a TextView be selectable AND contain links?

Community
  • 1
  • 1
devunwired
  • 62,780
  • 12
  • 127
  • 139