Android ClickableSpan not calling onClick
Asked Answered
V

5

194

I am creating a ClickableSpan, and it is displaying properly with the proper text underlined. However, the clicks are not registering. Do you know what I am doing wrong???

Thanks, Victor

Here is the code snippet:

view.setText("This is a test");
ClickableSpan span = new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        log("Clicked");
    }
};
view.getText().setSpan(span, 0, view.getText().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
Victor answered 27/12, 2011 at 5:2 Comment(0)
I
546

Have you tried setting the MovementMethod on the TextView that contains the span? You need to do that to make the clicking work...

tv.setMovementMethod(LinkMovementMethod.getInstance());
Intense answered 28/12, 2011 at 23:30 Comment(8)
Do not work well if tv is of type EditText, true you can click on the span but not edit this as normal.Whitsunday
thank a lot! It's work for me too! can you explicate me wherefore about this setting?Dufy
OF COURSE I need to set what the documentation calls an "arrow key handler" to make a click handler work. So obvious! (╯°□°)╯︵ ┻━┻Plectognath
It works, but I'll never really know why that is not the default behavior.Preece
And Google forgot to mention that calling setMovementMethod makes the "ellipsize" not work... So it seems the correct approach is to manually implement a TouchListener and take it from there...Hydro
I'm so sorry I misclicked downvote while I want to fav this answer. While this does make clicking work, it makes the whole TextView consume the click event, not just the span. If the TextView's background needs to be clickable (such as a Recycler ViewHolder), a bigger chunk of it (size of the TextView) than expected (size of the span) will be blocked. I end up creating a separate TextView for it...Romulus
Tkanks. You made my day bro.Aquatint
Thank you so much. Worked like a charm. I spent to much time on this. You saved my time. @AquatintWhangee
A
14

Kotlin util function:

fun setClickable(textView: TextView, subString: String, handler: () -> Unit, drawUnderline: Boolean = false) {
    val text = textView.text
    val start = text.indexOf(subString)
    val end = start + subString.length

    val span = SpannableString(text)
    span.setSpan(ClickHandler(handler, drawUnderline), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

    textView.linksClickable = true
    textView.isClickable = true
    textView.movementMethod = LinkMovementMethod.getInstance()

    textView.text = span
}

class ClickHandler(
        private val handler: () -> Unit,
        private val drawUnderline: Boolean
) : ClickableSpan() {
    override fun onClick(widget: View?) {
        handler()
    }

    override fun updateDrawState(ds: TextPaint?) {
        if (drawUnderline) {
            super.updateDrawState(ds)
        } else {
            ds?.isUnderlineText = false
        }
    }
}

Usage:

Utils.setClickable(textView, subString, {handleClick()})
Alto answered 3/10, 2018 at 11:49 Comment(0)
I
9

After some trial and error, the sequence of setting the tv.setMovementMethod(LinkMovementMethod.getInstance()); does matter.

Here's my full code

String stringTerms = getString(R.string.sign_up_terms);
Spannable spannable = new SpannableString(stringTerms);
int indexTermsStart = stringTerms.indexOf("Terms");
int indexTermsEnd = indexTermsStart + 18;
spannable.setSpan(new UnderlineSpan(), indexTermsStart, indexTermsEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(getColor(R.color.theme)), indexTermsStart, indexTermsEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Log.d(TAG, "TODO onClick.. Terms and Condition");
    }
}, indexTermsStart, indexTermsEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

int indexPolicyStart = stringTerms.indexOf("Privacy");
int indexPolicyEnd = indexPolicyStart + 14;
spannable.setSpan(new UnderlineSpan(), indexPolicyStart, indexPolicyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(getColor(R.color.theme)), indexPolicyStart, indexPolicyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Log.d(TAG, "TODO onClick.. Privacy Policy");
    }
}, indexPolicyStart, indexPolicyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

TextView textViewTerms = (TextView) findViewById(R.id.sign_up_terms_text);
textViewTerms.setText(spannable);
textViewTerms.setClickable(true);
textViewTerms.setMovementMethod(LinkMovementMethod.getInstance());
Imprecise answered 31/7, 2017 at 5:1 Comment(0)
T
6

Direct Approach in Kotlin

  val  textHeadingSpannable = SpannableString(resources.getString(R.string.travel_agent))


           val clickSpan = object : ClickableSpan(){
               override fun onClick(widget: View) {

                // Handel your click
               }
           }
            textHeadingSpannable.setSpan(clickSpan,104,136,Spannable.SPAN_INCLUSIVE_EXCLUSIVE)

            tv_contact_us_inquire_travel_agent.movementMethod = LinkMovementMethod.getInstance()
            tv_contact_us_inquire_travel_agent.text = textHeadingSpannable
Tupiguarani answered 2/5, 2019 at 11:39 Comment(0)
E
0

Well I had certain issues with LinkMovement for example the click is triggered on MotionEvent.ActionDown itself which is not really a click so I ended making a custom MovementMethod

class ClickMovementMethod implements MovementMethod {
    private final int normalColor, selectedColor;
    private float[] pressedCoordinate = null;

    private ClickMovementMethod(int normalColor, int selectedColor) {
        this.normalColor = normalColor;
        this.selectedColor = selectedColor;
    }

    @Override
    public void initialize(TextView widget, Spannable text) {

    }

    @Override
    public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) {
        return false;
    }

    @Override
    public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) {
        return false;
    }

    @Override
    public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
        return false;
    }

    @Override
    public void onTakeFocus(TextView widget, Spannable text, int direction) {

    }

    @Override
    public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
        return false;
    }

    @Override
    public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP) {
            int x = (int) event.getX();
            int y = (int) event.getY();


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

            Layout layout = widget.getLayout();

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

            ClickableSpan[] links = text.getSpans(off, off, ClickableSpan.class);
            if (action == MotionEvent.ACTION_UP) {
                if (pressedCoordinate != null) {
                    if (Math.abs(pressedCoordinate[0] - event.getX()) < 10 &&
                            Math.abs(pressedCoordinate[1] - event.getY()) < 10) {
                        widget.setLinkTextColor(normalColor);
                        links[0].onClick(widget);
                        pressedCoordinate = null;
                    } else {
                        widget.setLinkTextColor(normalColor);
                        pressedCoordinate = null;
                    }
                }
            } else if (links.length > 0) {
                widget.setLinkTextColor(selectedColor);
                pressedCoordinate = new float[]{event.getX(), event.getY()};
            }
        }
        return false;
    }

    @Override
    public boolean onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event) {
        return false;
    }

    @Override
    public boolean canSelectArbitrarily() {
        return false;
    }
}
Expecting answered 5/9, 2023 at 9:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.