Search code examples
androidtouchlinkmovementmethod

Redirect touch event on TextView with ClickableSpan to parent view


I have a RecyclerView with TextViews that could contain custom hashtags that should be clickable. So I have created subclass of TextView in which using Pattern I create ClickableSpan. In order for ClickableSpan to be active, I have added

setMovementMethod(LinkMovementMethod.getInstance())

This method changes properties of TextView:

setFocusable(true);
setClickable(true);
setLongClickable(true);

Click on links works, but it prevents ripple drawable to be shown on list item, and click on TextView outside of hashtags are ignored.

So I'm interested how can I redirect touches on TextView (all except on hashtag) to it's parent?


Solution

  • So I haven't found solution on the internet so I ended up implementing it by myself.

    So instead of using simple TextView with MovementMethod, I created a new class extending text view with onTouchEvent.

    public class MyTextView extends TextView {
    @Override
      public boolean onTouchEvent(@NonNull MotionEvent event) {
        if (linksActive) {
          Layout layout = this.getLayout();
          if (layout != null) {
            int line = layout.getLineForVertical((int) event.getY());
            int offset = layout.getOffsetForHorizontal(line, event.getX());
    
            if (getText() != null && getText() instanceof Spanned) {
              Spanned spanned = (Spanned) getText();
    
              ClickableSpan[] links = spanned.getSpans(offset, offset, ClickableSpan.class);
    
              if (links.length > 0) {
    
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                  return true;
                } else if (event.getAction() == MotionEvent.ACTION_UP) {
                  links[0].onClick(this);
                } else {
                  return super.onTouchEvent(event);
                }
              }
            }
          }
        }
    
        return super.onTouchEvent(event);
      }
    }
    

    Also I created a class that extends ClickableSpan which handles clicks:

    public class URLSpan extends ClickableSpan {
      private String url;
      private int spanType;
    
      public URLSpan(String url, int spanType) {
        this.url = url;
        this.spanType = spanType;
      }
    
      public int getType() {
        return spanType;
      }
    
      @Override
      public void onClick(@NonNull View widget) {
        if (widget instanceof OnSpanClickListener) {
          ((OnSpanClickListener) widget).onSpanClicked(url, type);
        } else {
          // ignored
        }
      }
    
    }
    

    And I this case I have to manage spans by myself (in setText() method)

    public static void formatPhoneNumbers(@NonNull Spannable spannable, @ColorInt int color) {
        Matcher matcher = MY_Patterns.PHONE_NO.matcher(spannable);
        while (matcher.find()) {
          if (!Linkify.sPhoneNumberMatchFilter.acceptMatch(spannable, matcher.start(), matcher.end())) {
            continue;
          }
    
          removeSpans(spannable, SpanType.PHONE_NO, matcher.start(), matcher.end());
    
          if (hasClickableSpans(spannable, matcher.start(), matcher.end())) {
            // ignore different clickable spans overlap
            continue;
          }
    
          final ForegroundColorSpan span = new ForegroundColorSpan(color);
          final ClickableSpan urlSpan = new URLSpan(matcher.group(), SpanType.PHONE_NO);
    
          spannable.setSpan(urlSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
          spannable.setSpan(span, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
      }
    
      private static void removeSpans(@NonNull Spannable spannable, int type, int start, int end) {
        URLSpan[] arr = spannable.getSpans(start, end, URLSpan.class);
        for (URLSpan span : arr) {
          if (span.getType() == type) {
            spannable.removeSpan(span);
          }
        }
      }
    
      private static boolean hasClickableSpans(@NonNull Spannable spannable, int start, int end) {
        ClickableSpan[] arr = spannable.getSpans(start, end, ClickableSpan.class);
        return arr.length > 0;
      }