1

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());
ישו אוהב אותך
  • 28,609
  • 11
  • 78
  • 96
Matt Robertson
  • 2,928
  • 5
  • 34
  • 62
  • 1. Individualy word are not clickable. 2. When I click on single all text get underline that means changing style onclick. So I'm not able understand your exact requirement. – Chitrang Jun 28 '16 at 05:59
  • The clickablespan makes individual words clickable. I am able to handle a click on each individual word and detect which word was clicked with this code. Now I just need to change the style of the word that was clicked, to show the user that it was in fact clicked. – Matt Robertson Jun 28 '16 at 10:58

1 Answers1

4

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());
Anirudha Agashe
  • 3,510
  • 2
  • 32
  • 47
  • Thanks, this solution works. However, the TextView will not scroll, even though I have maxLines and scrollbars set in the xml. – Matt Robertson Jun 30 '16 at 19:45
  • To make the textView scrollable surround it with scrollview. The other way to set movement method to scrollview won't work even if you extend ScrollingMovementMethod instead of LinkMovementMethod – Anirudha Agashe Jul 01 '16 at 07:42