8

The problem is that if i Linkify the textView the underliyng ScrollView don't listen the sweep Gestures I've setted.Is there a way to have Linkify without messing with the underliyng view's gestures? I tried to override ontouchEvent and return false to ACTION_MOVE but the scrollview's gesture needs the ACTION_DOWN and ACTION_UP event to function. Is there a way to achieve that?

weakwire
  • 9,284
  • 8
  • 53
  • 78

3 Answers3

47

Linkify applies to a movementMethod to the textView LinkMovementMethod. That movement method thought it implements a scrolling vertically method it overrides any other scrolling method the parent has. Although touchEvent can be dispached to the parent, the specific parent ScrollView needed the whole sequence ACTION_DOWN , ACTION_MOVE, ACTION_UP to perform (sweep detection).

So the solution to my problem is after Linkify to remove the textView's scrolling method and handle the LinkMovementMethod link detection action in onTouchEvent of the textView.

@override
public boolean onTouchEvent(MotionEvent event) {
        TextView widget = (TextView) this;
        Object text = widget.getText();
        if (text instanceof Spanned) {
            Spannable buffer = (Spannable) text;

            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);

                ClickableSpan[] link = buffer.getSpans(off, off,
                        ClickableSpan.class);

                if (link.length != 0) {
                    if (action == MotionEvent.ACTION_UP) {
                        link[0].onClick(widget);
                    } else if (action == MotionEvent.ACTION_DOWN) {
                         Selection.setSelection(buffer,
                                 buffer.getSpanStart(link[0]),
                                 buffer.getSpanEnd(link[0]));
                    }
                    return true;
                }
            }

        }

        return false;
    }

This way i have the Link_Click detection (performed only with the user touches the link and not the whole textview) and i don't have the whole LinkMovementMethod.

weakwire
  • 9,284
  • 8
  • 53
  • 78
  • i'm glad it finally helped someone :D – weakwire Apr 02 '12 at 20:05
  • 1
    Nice solution, clear and not hacky. I had to move `return true;` into the positive branch of internal if though to let the span be highlighted on steady touch. – A-Live Oct 19 '12 at 03:01
  • 2
    The mentioned "remove the textView's scrolling method" is done via `textView.setMovementMethod(null);` and has to be called _after_ you've set the textView's text. – Ridcully Jun 13 '13 at 18:47
  • Thank you, this has saved me a lot of time! – Ilja S. Nov 22 '13 at 14:30
3

@weakwire and @Ridicully answers are correct. I just created a small gist that you can re-use in your project.

This is the link: https://gist.github.com/amilcar-andrade/e4b76840da1dc92febfc

Amilcar Andrade
  • 1,131
  • 9
  • 16
  • I have ported this class to Xamarin/C#: https://gist.github.com/pmachapman/a988348a93933ac359ebe0d6a5314467 – Peter Jun 19 '19 at 02:56
0

There is a small bad thing that TextView::setText(...) method utilizing autoLink flag,

if (mAutoLinkMask != 0) {
        Spannable s2;

        if (type == BufferType.EDITABLE || text instanceof Spannable) {
            s2 = (Spannable) text;
        } else {
            s2 = mSpannableFactory.newSpannable(text);
        }

        if (Linkify.addLinks(s2, mAutoLinkMask)) {
            text = s2;
            type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;

            /*
             * We must go ahead and set the text before changing the
             * movement method, because setMovementMethod() may call
             * setText() again to try to upgrade the buffer type.
             */
            mText = text;

            // Do not change the movement method for text that support text selection as it
            // would prevent an arbitrary cursor displacement.
            if (mLinksClickable && !textCanBeSelected()) {
                setMovementMethod(LinkMovementMethod.getInstance());
            }
        }
    }

So I spent a time to understand, why i'm disabling links in ListView item, but it obtains a link sometimes!

You need to set that flag in needed value and then call a setText(...)

Mike
  • 479
  • 5
  • 15
  • 1
    It's a bit unclear what you're saying here. Is this an answer to the question or are you trying to ask a new question? – SuperBiasedMan Nov 25 '15 at 12:53
  • It's mostly a note, cause I found this topic when search for "Linkify textview in ListView" problem – Mike Nov 25 '15 at 13:55