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
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
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
Also, order matters
textView.setTextIsSelectable(true);
textView.setMovementMethod(LinkMovementMethod.getInstance());
Allows the content to be selectable and link clicks working just perfect
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]
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.
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());