Change the text color of a single ClickableSpan when pressed without affecting other ClickableSpans in the same TextView Change the text color of a single ClickableSpan when pressed without affecting other ClickableSpans in the same TextView android android

Change the text color of a single ClickableSpan when pressed without affecting other ClickableSpans in the same TextView


I finally found a solution that does everything I wanted. It is based on this answer.

This is my modified LinkMovementMethod that marks a span as pressed on the start of a touch event (MotionEvent.ACTION_DOWN) and unmarks it when the touch ends or when the touch location moves out of the span.

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() - textView.getTotalPaddingLeft() + textView.getScrollX();            int y = (int) event.getY() - textView.getTotalPaddingTop() + textView.getScrollY();            Layout layout = textView.getLayout();            int position = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x);            TouchableSpan[] link = spannable.getSpans(position, position, TouchableSpan.class);            TouchableSpan touchedSpan = null;            if (link.length > 0 && positionWithinTag(position, spannable, link[0])) {                touchedSpan = link[0];            }            return touchedSpan;        }        private boolean positionWithinTag(int position, Spannable spannable, Object tag) {            return position >= spannable.getSpanStart(tag) && position <= spannable.getSpanEnd(tag);        }    }

This needs to be applied to the TextView like so:

    yourTextView.setMovementMethod(new LinkTouchMovementMethod());

And this is the modified ClickableSpan that edits the draw state based on the pressed state set by the LinkTouchMovementMethod: (it also removes the underline from the links)

public abstract class TouchableSpan extends ClickableSpan {    private boolean mIsPressed;    private int mPressedBackgroundColor;    private int mNormalTextColor;    private int mPressedTextColor;    public TouchableSpan(int normalTextColor, int pressedTextColor, int pressedBackgroundColor) {        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 : 0xffeeeeee;        ds.setUnderlineText(false);    }}


Much simpler solution, IMO:

final int colorForThisClickableSpan = Color.RED; //Set your own conditional logic here.final ClickableSpan link = new ClickableSpan() {    @Override    public void onClick(final View view) {        //Do something here!    }    @Override    public void updateDrawState(TextPaint ds) {        super.updateDrawState(ds);        ds.setColor(colorForThisClickableSpan);    }};


All these solutions are too much work.

Just set android:textColorLink in your TextView to some selector. Then create a clickableSpan with no need to override updateDrawState(...). All done.

here a quick example:

In your strings.xml have a declared string like this:

<string name="mystring">This is my message%1$s these words are highlighted%2$s and awesome. </string>

then in your activity:

private void createMySpan(){    final String token = "#";    String myString = getString(R.string.mystring,token,token);    int start = myString.toString().indexOf(token);    //we do -1 since we are about to remove the tokens afterwards so it shifts    int finish = myString.toString().indexOf(token, start+1)-1;    myString = myString.replaceAll(token, "");    //create your spannable    final SpannableString spannable = new SpannableString(myString);    final ClickableSpan clickableSpan = new ClickableSpan() {            @Override            public void onClick(final View view) {                doSomethingOnClick();            }        };    spannable.setSpan(clickableSpan, start, finish, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);    mTextView.setMovementMethod(LinkMovementMethod.getInstance());    mTextView.setText(spannable);}

and heres the important parts ..declare a selector like this calling it myselector.xml:

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <item android:state_pressed="true" android:color="@color/gold"/>    <item android:color="@color/pink"/></selector>

And last in your TextView in xml do this:

 <TextView     android:id="@+id/mytextview"     android:background="@android:color/transparent"     android:text="@string/mystring"     android:textColorLink="@drawable/myselector" />

Now you can have a pressed state on your clickableSpan.