Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a TextView be selectable AND contain links?

Tags:

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 setMovementMethod(LinkMovementMethod.getInstance()), it is no longer selectable.

Please note, I don't mean making raw links clickable, but rather making actual words clickable by loading the TextView with HTML markup using something like setText(Html.fromHtml("<a href='http://stackoverflow.com'>Hello World!</a>")).

like image 222
oakes Avatar asked Apr 05 '13 14:04

oakes


People also ask

Is TextView clickable?

Just like Buttons and ImageViews we can add onClickListeners to TextViews by simply adding the attribute android:onClick="myMethod" to your TextView XML tag. The other way, TextView tv = (TextView) this.

What is the use of TextView?

A TextView displays text to the user and optionally allows them to edit it. A TextView is a complete text editor, however the basic class is configured to not allow editing.

What is a TextView?

TextView is the user interface which displays the text message on the screen to the user. It is based on the layout size, style, and color, etc. TextView is used to set and display the text according to our specifications.


2 Answers

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.

like image 75
Ranzed Avatar answered Sep 29 '22 19:09

Ranzed


I figured it out. You need to subclass LinkMovementMethod and add support for text selection. It's really unfortunate that it doesn't support it natively. I just overrode the relevant methods using the equivalent ones from the source code for ArrowKeyMovementMethod. I guess that's one benefit of Android being open source!

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

To use it, just instantiate it directly, like so:

textView.setMovementMethod(new CustomMovementMethod()); 
like image 20
oakes Avatar answered Sep 29 '22 18:09

oakes