18

I have a TextView with ClickableSpan in that both layout_height and layout_width is wrap_content.

When the text in TextView in not too long, it work fine. When the text is long enough such that it take 2 line, it also work fine but have some strange behavior.

That is when I clicked on the second line's empty space(not fill with text yet but part of TextView) , the ClickableSpan onClick() callback is called.

I do not expect this as I clicked the empty space only but not the spanned text. Although it does not affect much, I want to know what is behind.

I set the ClickableSpan with below code:

TextView tv = (TextView) findViewById(R.id.text);
tv.setText("TEXT TEXT TEXT TEXT");
SpannableStringBuilder ssb = new SpannableStringBuilder();
ssb.append(tv.getText());
ssb.setSpan(new TestClickableSpan(), ssb.length()-5, ssb.length(), 0);
tv.setText(ssb);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setOnTouchListener(new TextViewOnTouchListener());

The TextViewOnTouchListener:

class TextViewOnTouchListener implements OnTouchListener{

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.d("TextView", "onTouch");
            return false;
        }       
}

The TestClickableSpan:

class TestClickableSpan extends ClickableSpan{

        @Override
        public void onClick(View arg0) {
            Log.d("ClickableSpan", "Confirm OnClick: "+arg0.toString());
        }       
    }
Yeung
  • 2,202
  • 2
  • 27
  • 50

2 Answers2

24

I managed to solve it by extending LinkMovementMethod, and check whether the touch event offset is equals or bigger than the text lenght:

public class MovementMethod extends LinkMovementMethod {

    @Override
    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
        int action = event.getAction();

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            int x = (int) event.getX();
            int y = (int) event.getY();

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

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

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

            if (off >= widget.getText().length()) {
               // Return true so click won't be triggered in the leftover empty space
                return true;
            }
        }

        return super.onTouchEvent(widget, buffer, event);
    }
}
dor506
  • 5,246
  • 9
  • 44
  • 79
  • 5
    This is brilliant, thanks! One thing to watch out for that got me is you will need to override the static getInstance() function as well, otherwise you will still be returned an instance of the underlying LinkMovementMethod. I lost some time trying to figure that out so hopefully I save somebody else some headache. – martinp Jul 23 '15 at 23:54
  • 2
    @dor506 I love you! Thank you so much. – Dogcat Aug 19 '16 at 09:59
  • @martinp thanks man, I lost time there as well. The LinkMovementMethod's onTouchEvent kept being called and had me confused. – Mallika Khullar Oct 04 '16 at 09:40
4

Thanks for @dor506 for the answer, but it not work for gravity center, I have modified a little bit.

@Override
        public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
            int action = event.getAction();

            if (action == MotionEvent.ACTION_UP) {
                int x = (int) event.getX();
                int y = (int) event.getY();

                y -= widget.getTotalPaddingTop();
                y += widget.getScrollY();

                Layout layout = widget.getLayout();
                int line = layout.getLineForVertical(y);
                float lineLeft = layout.getLineLeft(line);
                float lineRight = layout.getLineRight(line);

                if (x > lineRight || (x >= 0 && x < lineLeft)) {
                    return true;
                }
            }

            return super.onTouchEvent(widget, buffer, event);
        }
Pavel Poley
  • 5,307
  • 4
  • 35
  • 66
  • 1
    Funny, I saw the accepted answer and after implementing it encountered the same issue with center gravity. Went to find a way to fix it but hopefully it took me 5 minutes to came back here and scroll a little bit further down. :) Working like a charm. – Saeed.re Dec 16 '20 at 15:27