Can a TextView be selectable AND contain links?

后端 未结 8 664
长情又很酷
长情又很酷 2020-12-08 21:47

I\'ve run into a problem with TextView. I can make it selectable using setTextIsSelectable(true), but when I enable links to be clicked via s

相关标签:
8条回答
  • 2020-12-08 21:58

    Here is my take on it for Kotlin (loosely based on @hai-zhang's answer). Simplified! See my gist for the better version. I currently use it for custom spans, not the HTML, and it is still relevant for me, especially when I need to pass the position of the user click to the span object.

    You need to set the movement method after setTextIsSelectable(true)

    /** Minimal version of Smart Movement that only has limited support of [ClickableSpan] */
    object SmartMovementMethodMinimal : ArrowKeyMovementMethod() {
    
        override fun onTouchEvent(widget: TextView?, buffer: Spannable?, event: MotionEvent?) =
            handleMotion(event!!, widget!!, buffer!!) || super.onTouchEvent(widget, buffer, event)
    
        private fun handleMotion(event: MotionEvent, widget: TextView, buffer: Spannable): Boolean {
            if (event.action == MotionEvent.ACTION_UP) {
                // Get click position
                val target = Point().apply {
                    x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX
                    y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY
                }
    
                // Get span line and offset
                val line = widget.layout.getLineForVertical(target.y)
                val offset = widget.layout.getOffsetForHorizontal(line, target.x.toFloat())
    
                if (event.action == MotionEvent.ACTION_UP) {
                    val spans = buffer.getSpans<ClickableSpan>(offset, offset)
                    if (spans.isNotEmpty()) {
                        spans.forEach { it.onClick(widget) }
                        return true
                    }
                }
            }
    
            return false
        }
    }
    

    More detailed and complex code with examples here: https://gist.github.com/sQu1rr/210f7e08dd939fa30dcd2209177ba875

    0 讨论(0)
  • 2020-12-08 22:02

    LinkMovementMethod() does not support text selection very well, even we can select the text, but after we scroll the textview, the selection will be lost.

    The best implementation is extending from ArrowKeyMovementMethod, which supports the text selection very well.

    Please see the details in here

    0 讨论(0)
  • 2020-12-08 22:02

    Also, order matters

    textView.setTextIsSelectable(true);
    textView.setMovementMethod(LinkMovementMethod.getInstance());
    

    Allows the content to be selectable and link clicks working just perfect

    0 讨论(0)
  • 2020-12-08 22:08

    would it be possible to associate the TextView with a URL? Is you have 10 TextView and 10 URLs it should be simple to write code that if TextView[3] is clicked it fires off an intent for webview (or browser) with URL[3]

    0 讨论(0)
  • 2020-12-08 22:11

    oakes's answer cause exception on double tap on textview

    java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0...

    I looked at the onTouchEvent impletentation in LinkMovementMethod and found that it removes selection when textview doesn't contain link. In this case selection starts from empty value and application crash when user try to change it.

    ...
    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;
    } else {
      Selection.removeSelection(buffer);
    }
    ...
    

    So i override onTouchEvent method, and it works fine.

    public class CustomMovementMethod extends LinkMovementMethod {
        @Override
        public boolean canSelectArbitrarily () {
            return true;
        }
    
        @Override
        public void initialize(TextView widget, Spannable text) {
            Selection.setSelection(text, text.length());
        }
    
        @Override
        public void onTakeFocus(TextView view, Spannable text, int dir) {
            if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
                if (view.getLayout() == null) {
                    // This shouldn't be null, but do something sensible if it is.
                    Selection.setSelection(text, text.length());
                }
            } else {
                Selection.setSelection(text, text.length());
            }
        }
    
        @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);
    
                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 Touch.onTouchEvent(widget, buffer, event);
        }
    }
    

    Hope it will be helpful for someone.

    0 讨论(0)
  • 2020-12-08 22:11

    The XML TextView should not have any link or any attributes that can be selected:

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    

    Then, set everything programmatically respecting the following order:

    textView.setText(Html.fromHtml(myHtml));
    Linkify.addLinks(textView, Linkify.WEB_URLS);
    textView.setTextIsSelectable(true); // API-11 and above
    textView.setMovementMethod(LinkMovementMethod.getInstance());
    
    0 讨论(0)
提交回复
热议问题