Matt Robertson Matt Robertson - 5 months ago 74
Android Question

Android ClickableSpan not changing style onclick

I have a TextView in which all words are individually clickable. I want to begin with every word unstyled. Upon clicking a word, the word should become and remain underlined. I am able to clear the default underline, but nothing happens upon click. (I am capturing and even processing the click, but I cannot get the Span style to change).

The relevant code is below. Thanks in advance for the help.

Custom ClickableSpan:

class WordSpan extends ClickableSpan {
private TextPaint textpaint;
public boolean clicked = false;

@Override
public void updateDrawState(TextPaint ds) {
textpaint = ds;
ds.setUnderlineText(false);

if (clicked)
ds.setUnderlineText(true);
}

@Override
public void onClick(View v) {}

public void setClicked(boolean c) {
clicked = c;
updateDrawState(textpaint);
}
}


From onCreate() I am parsing a txt file and adding each word to a TextView. Within this parsing loop I have the following code:

SpannableString ss = new SpannableString(word.toString());

WordSpan clickableSpan = new WordSpan() {
@Override
public void onClick(View view) {
setClicked(true);
view.invalidate();
}};

ss.setSpan(clickableSpan, 0, word.toString().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

tvText.append(ss);
tvText.append(" ");
}

tvText.setMovementMethod(LinkMovementMethod.getInstance());

Answer

To make individual word clickable you will have to add multiple clickable span to the spannable string. For example to make "foo" and "bar" individually clickable in single Textview you will have to add two clickable span, one for "foo" and other for "bar" and add them to spannable string.

In the example I have split the string using space for simplicity plus you would have to write logic for click of the span.

Clickable span which removes the underline. Additionally you can configure the background and text color on click. You can remove it if you are not going use it.

import android.text.TextPaint;
import android.text.style.ClickableSpan;

public abstract class TouchableSpan  extends ClickableSpan {
    private boolean mIsPressed;
    private int mPressedBackgroundColor;
    private int mNormalTextColor;
    private int mPressedTextColor;
    private int mBackgroundColor;

    public TouchableSpan(int normalTextColor,int backgroundColor, int pressedTextColor, int pressedBackgroundColor) {
        mBackgroundColor = backgroundColor;
        mNormalTextColor = normalTextColor;
        mPressedTextColor = pressedTextColor;
        mPressedBackgroundColor = pressedBackgroundColor;
    }

    public void setPressed(boolean isSelected) {
        mIsPressed = isSelected;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        super.updateDrawState(ds);
        ds.setColor(mIsPressed ? mPressedTextColor : mNormalTextColor);
        ds.bgColor = mIsPressed ? mPressedBackgroundColor : mBackgroundColor;
        ds.setUnderlineText(!mIsPressed);
    }
}

Create a LinkMovementMethod which will take care of your Span. If you remove the color provision you can alter this as well

import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.view.MotionEvent;
import android.widget.TextView;

public class LinkTouchMovementMethod extends LinkMovementMethod {

    private TouchableSpan mPressedSpan;

    @Override
    public boolean onTouchEvent(TextView textView, Spannable spannable, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            mPressedSpan = getPressedSpan(textView, spannable, event);
            if (mPressedSpan != null) {
                mPressedSpan.setPressed(true);
                Selection.setSelection(spannable, spannable.getSpanStart(mPressedSpan),
                        spannable.getSpanEnd(mPressedSpan));
            }
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            TouchableSpan touchedSpan = getPressedSpan(textView, spannable, event);
            if (mPressedSpan != null && touchedSpan != mPressedSpan) {
                mPressedSpan.setPressed(false);
                mPressedSpan = null;
                Selection.removeSelection(spannable);
            }
        } else {
            if (mPressedSpan != null) {
                mPressedSpan.setPressed(false);
                super.onTouchEvent(textView, spannable, event);
            }
            mPressedSpan = null;
            Selection.removeSelection(spannable);
        }
        return true;
    }

    private TouchableSpan getPressedSpan(TextView textView, Spannable spannable, MotionEvent event) {

        int x = (int) event.getX();
        int y = (int) event.getY();

        x -= textView.getTotalPaddingLeft();
        y -= textView.getTotalPaddingTop();

        x += textView.getScrollX();
        y += textView.getScrollY();

        Layout layout = textView.getLayout();
        int line = layout.getLineForVertical(y);
        int off = layout.getOffsetForHorizontal(line, x);

        TouchableSpan[] link = spannable.getSpans(off, off, TouchableSpan.class);
        TouchableSpan touchedSpan = null;
        if (link.length > 0) {
            touchedSpan = link[0];
        }
        return touchedSpan;
    }

}

Then you can use it in the following way:

TextView textView = (TextView)findViewById(R.id.hello_world);
String fooBar = "asdfasdfasdfasf asdfasfasfasd";
String[] clickSpans = fooBar.split(" ");
int clickSpanLength = clickSpans.length;
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
int totalLength = 0;
int normalColor = getResources().getColor(android.R.color.black);
int clickColor = getResources().getColor(android.R.color.holo_blue_bright);
String separator = " , ";
int separatorLength = separator.length();
for (int i = 0; i < clickSpanLength; i++) {
    int currentWordLength = clickSpans[i].length();
    spannableStringBuilder.append(clickSpans[i]);
    if (i < clickSpanLength - 1) {
        spannableStringBuilder.append(" , ");
    }

    spannableStringBuilder.setSpan(new TouchableSpan(normalColor, Color.TRANSPARENT, clickColor, Color.TRANSPARENT) {
        @Override
        public void onClick(View widget) {

        }
    }, totalLength, totalLength + currentWordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
    totalLength = totalLength + currentWordLength + separatorLength;
}

textView.setText(spannableStringBuilder);
textView.setMovementMethod(new LinkTouchMovementMethod());
Comments