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

Actualmente, estoy procesando la entrada de HTML en un TextView de esta manera:

tv.setText(Html.fromHtml("https://stackoverflow.com/questions/1697084/handle-textview-link-click-in-my-android-app/test")); 

El HTML que se muestra se me proporciona a través de un recurso externo, por lo que no puedo cambiar las cosas como lo haré, pero puedo, por supuesto, manipular algunas expresiones regulares con el HTML, para cambiar el valor href, por ejemplo, a otra cosa.

Lo que quiero es poder manejar un enlace, hacer clic directamente desde dentro de la aplicación, en lugar de hacer que el enlace abra una ventana del navegador. ¿Es esto alcanzable en absoluto? Supongo que sería posible establecer el protocolo del href-value en algo como “myApp: //”, y luego registrar algo que permita que mi aplicación maneje ese protocolo. Si esta es la mejor manera, me gustaría saber cómo se hace, pero espero que haya una manera más fácil de decir: “cuando se hace clic en un enlace en esta vista de texto, quiero plantear un evento que recibe el valor href del enlace como parámetro de entrada ”

Llegando a esto casi un año después, hay una manera diferente en que resolví mi problema particular. Como quería que el enlace fuera manejado por mi propia aplicación, hay una solución que es un poco más simple.

Además del filtro de intención predeterminado, simplemente dejo que mi actividad objective escuche ACTION_VIEW intents, y específicamente, aquellos con el esquema com.package.name

      

Esto significa que los enlaces que comienzan con com.package.name:// serán gestionados por mi actividad.

Entonces, todo lo que tengo que hacer es construir una URL que contenga la información que quiero transmitir:

 com.package.name://action-to-perform/id-that-might-be-needed/ 

En mi actividad objective, puedo recuperar esta dirección:

 Uri data = getIntent().getData(); 

En mi ejemplo, simplemente podría verificar data para valores nulos, porque cuando no sea nulo, sabré que fue invocado por medio de dicho enlace. A partir de ahí, extraigo las instrucciones que necesito de la URL para poder mostrar los datos apropiados.

Otra forma, toma un poco de Linkify pero te permite personalizar tu manejo.

Clase de span personalizada:

 public class ClickSpan extends ClickableSpan { private OnClickListener mListener; public ClickSpan(OnClickListener listener) { mListener = listener; } @Override public void onClick(View widget) { if (mListener != null) mListener.onClick(); } public interface OnClickListener { void onClick(); } } 

Función de ayudante:

 public static void clickify(TextView view, final String clickableText, final ClickSpan.OnClickListener listener) { CharSequence text = view.getText(); String string = text.toString(); ClickSpan span = new ClickSpan(listener); int start = string.indexOf(clickableText); int end = start + clickableText.length(); if (start == -1) return; if (text instanceof Spannable) { ((Spannable)text).setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { SpannableString s = SpannableString.valueOf(text); s.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); view.setText(s); } MovementMethod m = view.getMovementMethod(); if ((m == null) || !(m instanceof LinkMovementMethod)) { view.setMovementMethod(LinkMovementMethod.getInstance()); } } 

Uso:

  clickify(textView, clickText,new ClickSpan.OnClickListener() { @Override public void onClick() { // do something } }); 

si hay múltiples enlaces en la vista de texto. Por ejemplo, textview tiene “https: //” y “tel no”, podemos personalizar el método LinkMovement y manejar los clics de palabras según un patrón. Adjunto está el Método de Movimiento de Enlace personalizado.

 public class CustomLinkMovementMethod extends LinkMovementMethod { private static Context movementContext; private static CustomLinkMovementMethod linkMovementMethod = new CustomLinkMovementMethod(); public boolean onTouchEvent(android.widget.TextView widget, android.text.Spannable buffer, android.view.MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_UP) { 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); URLSpan[] link = buffer.getSpans(off, off, URLSpan.class); if (link.length != 0) { String url = link[0].getURL(); if (url.startsWith("https")) { Log.d("Link", url); Toast.makeText(movementContext, "Link was clicked", Toast.LENGTH_LONG).show(); } else if (url.startsWith("tel")) { Log.d("Link", url); Toast.makeText(movementContext, "Tel was clicked", Toast.LENGTH_LONG).show(); } else if (url.startsWith("mailto")) { Log.d("Link", url); Toast.makeText(movementContext, "Mail link was clicked", Toast.LENGTH_LONG).show(); } return true; } } return super.onTouchEvent(widget, buffer, event); } public static android.text.method.MovementMethod getInstance(Context c) { movementContext = c; return linkMovementMethod; } 

Esto debe invocarse desde la vista de texto de la siguiente manera:

 textViewObject.setMovementMethod(CustomLinkMovementMethod.getInstance(context)); 

Aquí hay una solución más genérica basada en la respuesta de @Arun

 public abstract class TextViewLinkHandler extends LinkMovementMethod { public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { if (event.getAction() != MotionEvent.ACTION_UP) return super.onTouchEvent(widget, buffer, 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); URLSpan[] link = buffer.getSpans(off, off, URLSpan.class); if (link.length != 0) { onLinkClick(link[0].getURL()); } return true; } abstract public void onLinkClick(String url); } 

Para usarlo simplemente implemente onLinkClick de la clase TextViewLinkHandler . Por ejemplo:

  textView.setMovementMethod(new TextViewLinkHandler() { @Override public void onLinkClick(String url) { Toast.makeText(textView.getContext(), url, Toast.LENGTH_SHORT); } }); 

Es muy simple agregar esta línea a tu código:

 tv.setMovementMethod(LinkMovementMethod.getInstance()); 

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

 TextView tv = (TextView) v.findViewById(R.id.textview); tv.setText(Html.fromHtml("https://stackoverflow.com/questions/1697084/handle-textview-link-click-in-my-android-app/test")); textView.setMovementMethod(new TextViewClickMovement(this, context)); 

Campo de golf

¡Espero que esto ayude! Puedes encontrar el código aquí .

Solo para compartir una solución alternativa usando una biblioteca que creé. Con Textoo , esto se puede lograr como:

 TextView locNotFound = Textoo .config((TextView) findViewById(R.id.view_location_disabled)) .addLinksHandler(new LinksHandler() { @Override public boolean onClick(View view, String url) { if ("internal://settings/location".equals(url)) { Intent locSettings = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivity(locSettings); return true; } else { return false; } } }) .apply(); 

O con fuente HTML dinámica:

 String htmlSource = "Links: Google"; Spanned linksLoggingText = Textoo .config(htmlSource) .parseHtml() .addLinksHandler(new LinksHandler() { @Override public boolean onClick(View view, String url) { Log.i("MyActivity", "Linking to google..."); return false; // event not handled. Continue default processing ie link to google } }) .apply(); textView.setText(linksLoggingText); 

para quien busca más opciones aquí hay uno

 // Set text within a `TextView` TextView textView = (TextView) findViewById(R.id.textView); textView.setText("Hey @sarah, where did @jim go? #lost"); // Style clickable spans based on pattern new PatternEditableBuilder(). addPattern(Pattern.compile("\\@(\\w+)"), Color.BLUE, new PatternEditableBuilder.SpannableClickedListener() { @Override public void onSpanClicked(String text) { Toast.makeText(MainActivity.this, "Clicked username: " + text, Toast.LENGTH_SHORT).show(); } }).into(textView); 

RECURSO: CodePath

Cambié el color de TextView a azul usando, por ejemplo:

 android:textColor="#3399FF" 

en el archivo xml Cómo hacer que se destaque se explica aquí .

Luego use su propiedad onClick para especificar un método (supongo que podría llamar a setOnClickListener(this) otra manera), por ejemplo:

 myTextView.setOnClickListener(new OnClickListener() { public void onClick(View v) { doSomething(); } }); 

En ese método, puedo hacer lo que quiera como normal, como lanzar un bash. Tenga en cuenta que todavía tiene que hacer el normal myTextView.setMovementMethod(LinkMovementMethod.getInstance()); cosa, como en el método onCreate () de su actividad.

La mejor forma que usé y siempre funcionó para mí

 android:autoLink="web" 

Esta respuesta extiende la excelente solución de Jonathan S:

Puede usar el siguiente método para extraer enlaces del texto:

 private static ArrayList getLinksFromText(String text) { ArrayList links = new ArrayList(); String regex = "\(?\b((http|https)://www[.])[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(text); while (m.find()) { String urlStr = m.group(); if (urlStr.startsWith("(") && urlStr.endsWith(")")) { urlStr = urlStr.substring(1, urlStr.length() - 1); } links.add(urlStr); } return links; } 

Esto se puede usar para eliminar uno de los parámetros en el método clickify() :

 public static void clickify(TextView view, final ClickSpan.OnClickListener listener) { CharSequence text = view.getText(); String string = text.toString(); ArrayList linksInText = getLinksFromText(string); if (linksInText.isEmpty()){ return; } String clickableText = linksInText.get(0); ClickSpan span = new ClickSpan(listener,clickableText); int start = string.indexOf(clickableText); int end = start + clickableText.length(); if (start == -1) return; if (text instanceof Spannable) { ((Spannable) text).setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { SpannableString s = SpannableString.valueOf(text); s.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); view.setText(s); } MovementMethod m = view.getMovementMethod(); if ((m == null) || !(m instanceof LinkMovementMethod)) { view.setMovementMethod(LinkMovementMethod.getInstance()); } } 

Algunos cambios en ClickSpan:

 public static class ClickSpan extends ClickableSpan { private String mClickableText; private OnClickListener mListener; public ClickSpan(OnClickListener listener, String clickableText) { mListener = listener; mClickableText = clickableText; } @Override public void onClick(View widget) { if (mListener != null) mListener.onClick(mClickableText); } public interface OnClickListener { void onClick(String clickableText); } } 

Ahora puede simplemente configurar el texto en la Vista de texto y luego agregarle un oyente:

 TextViewUtils.clickify(textWithLink,new TextUtils.ClickSpan.OnClickListener(){ @Override public void onClick(String clickableText){ //action... } }); 
 public static void setTextViewFromHtmlWithLinkClickable(TextView textView, String text) { Spanned result; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { result = Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY); } else { result = Html.fromHtml(text); } textView.setText(result); textView.setMovementMethod(LinkMovementMethod.getInstance()); } 

Ejemplo: supongamos que ha establecido un texto en la vista de texto y desea proporcionar un enlace sobre una expresión de texto en particular: “Haga clic en #facebook que lo llevará a facebook.com”

En el diseño xml:

  

En actividad:

 String text = "Click on #facebook will take you to facebook.com"; tv.setText(text); Pattern tagMatcher = Pattern.compile("[#]+[A-Za-z0-9-_]+\\b"); String newActivityURL = "content://ankit.https://stackoverflow.com/questions/1697084/handle-textview-link-click-in-my-android-app/testactivity/"; Linkify.addLinks(tv, tagMatcher, newActivityURL); 

Cree también un proveedor de tags como:

 public class TagProvider extends ContentProvider { @Override public int delete(Uri arg0, String arg1, String[] arg2) { // TODO Auto-generated method stub return 0; } @Override public String getType(Uri arg0) { return "vnd.android.cursor.item/vnd.cc.tag"; } @Override public Uri insert(Uri arg0, ContentValues arg1) { // TODO Auto-generated method stub return null; } @Override public boolean onCreate() { // TODO Auto-generated method stub return false; } @Override public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) { // TODO Auto-generated method stub return null; } @Override public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { // TODO Auto-generated method stub return 0; } } 

En el archivo de manifiesto, realice como entrada para el proveedor y la actividad de prueba como:

         

Ahora cuando hagas clic en #facebook, invocará https://stackoverflow.com/questions/1697084/handle-textview-link-click-in-my-android-app/testamento. Y en la actividad de prueba puede obtener los datos como:

 Uri uri = getIntent().getData();