Android TextView con enlaces seleccionables: cómo capturar clics?

Tengo un TextView que muestra HTML básico, que contiene más de 2 enlaces. Necesito capturar los clics en los enlaces y abrirlos, en mi propio WebView interno (no en el navegador predeterminado).

El método más común para manejar la representación de enlaces parece ser el siguiente:

String str_links = "Google
Facebook"; text_view.setLinksClickable(true); text_view.setMovementMethod(LinkMovementMethod.getInstance()); text_view.setText( Html.fromHtml( str_links ) );

Sin embargo, esto hace que los enlaces se abran en el navegador web interno predeterminado (que muestra el cuadro de diálogo “Acción completa usando …”).

Intenté implementar un onClickListener, que se dispara correctamente cuando se hace clic en el enlace, pero no sé cómo determinar en qué enlace se hizo clic …

 text_view.setOnClickListener(new OnClickListener(){ public void onClick(View v) { // what now...? } }); 

Alternativamente, intenté crear una clase de LinkMovementMethod personalizada e implementar onTouchEvent …

 public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) { String url = text.toString(); // this doesn't work because the text is not necessarily a URL, or even a single link... // eg, I don't know how to extract the clicked link from the greater paragraph of text return false; } 

Ideas?


Ejemplo de solución

Se me ocurrió una solución que analiza los enlaces de una cadena HTML y hace que se pueda hacer clic en ellos, y luego le permite responder a la URL.

En función de otra respuesta , aquí hay una función setTextViewHTML () que analiza los enlaces de una cadena HTML y hace que se pueda hacer clic en ellos, y luego le permite responder a la URL.

 protected void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span) { int start = strBuilder.getSpanStart(span); int end = strBuilder.getSpanEnd(span); int flags = strBuilder.getSpanFlags(span); ClickableSpan clickable = new ClickableSpan() { public void onClick(View view) { // Do something with span.getURL() to handle the link click... } }; strBuilder.setSpan(clickable, start, end, flags); strBuilder.removeSpan(span); } protected void setTextViewHTML(TextView text, String html) { CharSequence sequence = Html.fromHtml(html); SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence); URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class); for(URLSpan span : urls) { makeLinkClickable(strBuilder, span); } text.setText(strBuilder); text.setMovementMethod(LinkMovementMethod.getInstance()); } 

Esto se puede resolver simplemente usando Spannable String. Lo que realmente quieres hacer (Business Requirement) no está claro para mí, por lo que el siguiente código no dará una respuesta exacta a tu situación, pero estoy seguro de que te dará una idea y usted podrá resolver su problema basándose en el siguiente código.

Mientras lo haces, también obtengo algunos datos a través de la respuesta HTTP y agregué un texto subrayado adicional en mi caso “más” y este texto subrayado abrirá el navegador web al hacer clic en evento. Espero que esto te ayude.

 TextView decription = (TextView)convertView.findViewById(R.id.library_rss_expan_chaild_des_textView); String dec=d.get_description()+"more"; CharSequence sequence = Html.fromHtml(dec); SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence); UnderlineSpan[] underlines = strBuilder.getSpans(0, 10, UnderlineSpan.class); for(UnderlineSpan span : underlines) { int start = strBuilder.getSpanStart(span); int end = strBuilder.getSpanEnd(span); int flags = strBuilder.getSpanFlags(span); ClickableSpan myActivityLauncher = new ClickableSpan() { public void onClick(View view) { Log.e(TAG, "on click"); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(d.get_link())); mContext.startActivity(intent); } }; strBuilder.setSpan(myActivityLauncher, start, end, flags); } decription.setText(strBuilder); decription.setLinksClickable(true); decription.setMovementMethod(LinkMovementMethod.getInstance()); 

Has hecho lo siguiente:

 text_view.setMovementMethod(LinkMovementMethod.getInstance()); text_view.setText( Html.fromHtml( str_links ) ); 

¿Has probado en orden inverso como se muestra a continuación?

 text_view.setText( Html.fromHtml( str_links ) ); text_view.setMovementMethod(LinkMovementMethod.getInstance()); 

y sin:

 text_view.setLinksClickable(true); 

He tenido el mismo problema pero un montón de texto mezclado con pocos enlaces y correos electrónicos. Creo que usar ‘autoLink’ es una forma más fácil y limpia de hacerlo:

  text_view.setText( Html.fromHtml( str_links ) ); text_view.setLinksClickable(true); text_view.setAutoLinkMask(Linkify.ALL); //to open links 

Puede establecer Linkify.EMAIL_ADDRESSES o Linkify.WEB_URLS si solo hay uno de ellos que desea usar o configurar desde el diseño XML

  android:linksClickable="true" android:autoLink="web|email" 

Las opciones disponibles son: ninguna, web, correo electrónico, teléfono, mapa, todo

Solución

Implementé una pequeña clase con la ayuda de la cual puede manejar largos clics en TextView y Taps en los enlaces en TextView.

Diseño

 TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:autoLink="all"/> 

TextViewClickMovement.java

 import android.content.Context; import android.text.Layout; import android.text.Spannable; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.Patterns; import android.view.GestureDetector; import android.view.MotionEvent; import android.widget.TextView; public class TextViewClickMovement extends LinkMovementMethod { private final String TAG = TextViewClickMovement.class.getSimpleName(); private final OnTextViewClickMovementListener mListener; private final GestureDetector mGestureDetector; private TextView mWidget; private Spannable mBuffer; public enum LinkType { /** Indicates that phone link was clicked */ PHONE, /** Identifies that URL was clicked */ WEB_URL, /** Identifies that Email Address was clicked */ EMAIL_ADDRESS, /** Indicates that none of above mentioned were clicked */ NONE } /** * Interface used to handle Long clicks on the {@link TextView} and taps * on the phone, web, mail links inside of {@link TextView}. */ public interface OnTextViewClickMovementListener { /** * This method will be invoked when user press and hold * finger on the {@link TextView} * * @param linkText Text which contains link on which user presses. * @param linkType Type of the link can be one of {@link LinkType} enumeration */ void onLinkClicked(final String linkText, final LinkType linkType); /** * * @param text Whole text of {@link TextView} */ void onLongClick(final String text); } public TextViewClickMovement(final OnTextViewClickMovementListener listener, final Context context) { mListener = listener; mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener()); } @Override public boolean onTouchEvent(final TextView widget, final Spannable buffer, final MotionEvent event) { mWidget = widget; mBuffer = buffer; mGestureDetector.onTouchEvent(event); return false; } /** * Detects various gestures and events. * Notify users when a particular motion event has occurred. */ class SimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent event) { // Notified when a tap occurs. return true; } @Override public void onLongPress(MotionEvent e) { // Notified when a long press occurs. final String text = mBuffer.toString(); if (mListener != null) { Log.d(TAG, "----> Long Click Occurs on TextView with ID: " + mWidget.getId() + "\n" + "Text: " + text + "\n<----"); mListener.onLongClick(text); } } @Override public boolean onSingleTapConfirmed(MotionEvent event) { // Notified when tap occurs. final String linkText = getLinkText(mWidget, mBuffer, event); LinkType linkType = LinkType.NONE; if (Patterns.PHONE.matcher(linkText).matches()) { linkType = LinkType.PHONE; } else if (Patterns.WEB_URL.matcher(linkText).matches()) { linkType = LinkType.WEB_URL; } else if (Patterns.EMAIL_ADDRESS.matcher(linkText).matches()) { linkType = LinkType.EMAIL_ADDRESS; } if (mListener != null) { Log.d(TAG, "----> Tap Occurs on TextView with ID: " + mWidget.getId() + "\n" + "Link Text: " + linkText + "\n" + "Link Type: " + linkType + "\n<----"); mListener.onLinkClicked(linkText, linkType); } return false; } private String getLinkText(final TextView widget, final Spannable buffer, final MotionEvent event) { 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) { return buffer.subSequence(buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0])).toString(); } return ""; } } } 

Uso

 String str_links = "Google
Facebook"; text_view.setText( Html.fromHtml( str_links ) ); text_view.setMovementMethod(new TextViewClickMovement(this, context));

Campo de golf

Espero que este helops! Puedes encontrar el código aquí .

Una solución mucho más limpia y mejor, utilizando la biblioteca Linkify nativa.

Ejemplo:

 Linkify.addLinks(mTextView, Linkify.ALL); 

Estoy usando solo textView, y establezco span para url y manejo click.

Encontré una solución muy elegante aquí, sin linkify, según eso sé qué parte de la cadena quiero vincular

manejar el enlace textview hacer clic en mi aplicación de Android

en kotlin:

 fun linkify(view: TextView, url: String, context: Context) { val text = view.text val string = text.toString() val span = ClickSpan(object : ClickSpan.OnClickListener { override fun onClick() { // handle your click } }) val start = string.indexOf(url) val end = start + url.length if (start == -1) return if (text is Spannable) { text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) text.setSpan(ForegroundColorSpan(ContextCompat.getColor(context, R.color.orange)), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } else { val s = SpannableString.valueOf(text) s.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) s.setSpan(ForegroundColorSpan(ContextCompat.getColor(context, R.color.orange)), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) view.text = s } val m = view.movementMethod if (m == null || m !is LinkMovementMethod) { view.movementMethod = LinkMovementMethod.getInstance() } } class ClickSpan(private val mListener: OnClickListener) : ClickableSpan() { override fun onClick(widget: View) { mListener.onClick() } interface OnClickListener { fun onClick() } } 

y uso: linkify (yourTextView, urlString, context)