TextView Android con collegamenti cliccabili: come acquisire i clic?


116

Ho un TextView che esegue il rendering di HTML di base, contenente 2+ collegamenti. Devo acquisire i clic sui collegamenti e aprire i collegamenti - nella mia WebView interna (non nel browser predefinito).

Il metodo più comune per gestire il rendering dei link sembra essere questo:

String str_links = "<a href='http://google.com'>Google</a><br /><a href='http://facebook.com'>Facebook</a>";
text_view.setLinksClickable(true);
text_view.setMovementMethod(LinkMovementMethod.getInstance());
text_view.setText( Html.fromHtml( str_links ) );

Tuttavia, questo fa sì che i collegamenti si aprano nel browser web interno predefinito (che mostra la finestra di dialogo "Completa azione usando ...").

Ho provato a implementare un onClickListener, che viene attivato correttamente quando si fa clic sul collegamento, ma non so come determinare QUALE collegamento è stato cliccato ...

text_view.setOnClickListener(new OnClickListener(){

    public void onClick(View v) {
        // what now...?
    }

});

In alternativa, ho provato a creare una classe LinkMovementMethod personalizzata e ad implementare 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;
}

Idee?


Soluzione di esempio

Ho trovato una soluzione che analizza i collegamenti da una stringa HTML e li rende cliccabili, quindi ti consente di rispondere all'URL.


1
Perché non usi Spannable String. ??
Renjith

1
In realtà, l'HTML è fornito da un server remoto, non generato dalla mia applicazione.
Zane Claes

La tua soluzione di esempio è molto utile; utilizzando tale approccio, acquisisco i clic in modo corretto e posso avviare un'altra attività, con parametri, a seconda del collegamento selezionato. (Il punto chiave da capire era "Fai qualcosa con span.getURL()".) Potresti anche pubblicarlo come risposta, poiché è migliore della risposta attualmente accettata!
Jonik

Risposte:


223

In base a un'altra risposta , ecco una funzione setTextViewHTML () che analizza i collegamenti da una stringa HTML e li rende selezionabili, quindi ti consente di rispondere all'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());       
}

5
Ha funzionato alla grande. Con questo approccio (a differenza delle altre risposte), sono riuscito a 1) catturare i clic e 2) avviare un'altra Attività, con parametri, a seconda di quale link è stato cliccato.
Jonik

perfetto ... mi ha salvato la giornata
maverickosama92

Meraviglioso, ma se lo applichi a un ListView (voglio dire, al TextView interno di ogni elemento), rende l'elenco non cliccabile, sebbene i collegamenti siano ancora cliccabili
voghDev

@voghDev questo accade con ListViews quando un Views' focusableè impostato vero. Questo di solito accade con Buttons / ImageButtons. Prova a chiamare setFocusable(false)il tuo TextView.
Sufian,

Assicurati di utilizzare text.setMovementMethod(LinkMovementMethod.getInstance());se non utilizzi URLSpan
rajath,

21

Hai fatto come segue:

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

hai provato in ordine inverso come mostrato di seguito?

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

e senza:

text_view.setLinksClickable(true);

3
Funziona ma senza alcun segnale che l'utente abbia cliccato sul link, ho bisogno di un segnale / animazione / evidenziazione quando il link viene cliccato ... cosa devo fare?
spada laser

@StarWars puoi usare (StateList) [ developer.android.com/intl/pt-br/guide/topics/resources/… in puro Android, ma con HTML non lo so.
ademar111190

20

Questo può essere risolto semplicemente usando Spannable String. Quello che vuoi veramente fare (Requisiti aziendali) non è un po 'chiaro per me, quindi il codice seguente non darà una risposta esatta alla tua situazione, ma sono abbastanza sicuro che ti darà un'idea e sarai in grado di risolvere il tuo problema in base al seguente codice.

Mentre lo fai, ricevo anche alcuni dati tramite la risposta HTTP e ho aggiunto del testo sottolineato aggiuntivo nel mio caso "altro" e questo testo sottolineato aprirà il browser web all'evento del clic. Spero che questo ti aiuti.

TextView decription = (TextView)convertView.findViewById(R.id.library_rss_expan_chaild_des_textView);
String dec=d.get_description()+"<a href='"+d.get_link()+"'><u>more</u></a>";
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());

Grande! L'ho modificato per il mio caso. Modificherò il mio post per includere il codice.
Zane Claes

esiste una soluzione simile che può essere utilizzata all'interno di xml?
sviluppatore Android

Ho funzionato con la versione modificata di OP (nella domanda), non con questa. (Con questa versione, i clic sono passati direttamente alla finestra di dialogo "completa l'azione utilizzando".)
Jonik

Ho usato questa logica, tuttavia ho dovuto sostituire UnderlineSpan con URLSpan. Inoltre, era necessario rimuovere i vecchi intervalli da SpannableStringBuilder.
Ray

4
Qual è la variabile "d" qui?
Salman Khan

15

Ho avuto lo stesso problema ma molto testo mescolato con pochi link ed e-mail. Penso che usare 'autoLink' sia un modo più semplice e pulito per farlo:

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

Puoi impostare Linkify.EMAIL_ADDRESSES o Linkify.WEB_URLS se ce n'è solo uno che desideri utilizzare o impostare dal layout XML

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

Le opzioni disponibili sono: nessuna, web, e-mail, telefono, mappa, tutte


1
Ciao, c'è un modo per intercettare l'intento sparato al momento del clic di un collegamento
Manmohan Soni

1
Sono passati 6 anni da questa risposta .. Ovviamente potrebbe essere cambiato nell'ultima versione di Android ^^ Non significa che non funzionasse allora ^^
Jordi

8

Soluzione

Ho implementato una piccola classe con l'aiuto della quale puoi gestire lunghi clic su TextView stesso e Tap sui collegamenti in TextView.

disposizione

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 = "<a href='http://google.com'>Google</a><br /><a href='http://facebook.com'>Facebook</a>";
text_view.setText( Html.fromHtml( str_links ) );
text_view.setMovementMethod(new TextViewClickMovement(this, context));

link

Spero che questo aiuti! Puoi trovare il codice qui .


Per favore, controlla di nuovo il tuo codice, dalla riga text_view.setMovementMethod(new TextViewClickMovement(this, context));; Android Studio si lamenta del fatto che contextnon è stato possibile risolvere.
X09

Se copi il codice sorgente da bitbucket, dovresti cambiare la posizione del contesto e dell'ascoltatore come questo text_view.setMovementMethod (new TextViewClickMovement (context. This));
Victor Apoyan

Questo analizzerà due contesti per i parametri. Non ha funzionato, signore. Anche se la risposta accettata sta funzionando per me ora
X09

Grazie per la tua risposta, signore, la migliore in circolazione per questo tipo, grazie!
Vulovic Vukasin


6

Ho creato una semplice funzione di estensione in Kotlin per catturare i clic sul collegamento URL in un TextView applicando un nuovo callback agli elementi URLSpan.

strings.xml (collegamento di esempio nel testo)

<string name="link_string">this is my link: <a href="https://www.google.com/">CLICK</a></string>

Assicurati che il testo con spanning sia impostato su TextView prima di chiamare "handleUrlClicks"

textView.text = getString(R.string.link_string)

Questa è la funzione di estensione:

/**
 * Searches for all URLSpans in current text replaces them with our own ClickableSpans
 * forwards clicks to provided function.
 */
fun TextView.handleUrlClicks(onClicked: ((String) -> Unit)? = null) {
    //create span builder and replaces current text with it
    text = SpannableStringBuilder.valueOf(text).apply {
        //search for all URL spans and replace all spans with our own clickable spans
        getSpans(0, length, URLSpan::class.java).forEach {
            //add new clickable span at the same position
            setSpan(
                object : ClickableSpan() {
                    override fun onClick(widget: View) {
                        onClicked?.invoke(it.url)
                    }
                },
                getSpanStart(it),
                getSpanEnd(it),
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE
            )
            //remove old URLSpan
            removeSpan(it)
        }
    }
    //make sure movement method is set
    movementMethod = LinkMovementMethod.getInstance()
}

Questo è come lo chiamo io:

textView.handleUrlClicks { url ->
    Timber.d("click on found span: $url")
}

5

Se stai usando Kotlin, ho scritto una semplice estensione per questo caso:

/**
 * Enables click support for a TextView from a [fullText] String, which one containing one or multiple URLs.
 * The [callback] will be called when a click is triggered.
 */
fun TextView.setTextWithLinkSupport(
    fullText: String,
    callback: (String) -> Unit
) {
    val spannable = SpannableString(fullText)
    val matcher = Patterns.WEB_URL.matcher(spannable)
    while (matcher.find()) {
        val url = spannable.toString().substring(matcher.start(), matcher.end())
        val urlSpan = object : URLSpan(fullText) {
            override fun onClick(widget: View) {
                callback(url)
            }
        }
        spannable.setSpan(urlSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    }
    text = spannable
    movementMethod = LinkMovementMethod.getInstance() // Make link clickable
}

Uso:

yourTextView.setTextWithLinkSupport("click on me: https://www.google.fr") {
   Log.e("URL is $it")
}

1

Un approccio alternativo, molto più semplice (per sviluppatori pigri come me;)

abstract class LinkAwareActivity : AppCompatActivity() {

    override fun startActivity(intent: Intent?) {
        if(Intent.ACTION_VIEW.equals(intent?.action) && onViewLink(intent?.data.toString(), intent)){
            return
        }

        super.startActivity(intent)
    }

    // return true to consume the link (meaning to NOT call super.startActivity(intent))
    abstract fun onViewLink(url: String?, intent: Intent?): Boolean 
}

Se necessario, puoi anche controllare lo schema / il tipo MIME dell'intento


0

Sto usando solo textView e imposta l'intervallo per l'URL e gestisci il clic.

Ho trovato una soluzione molto elegante qui, senza linkify - in base a ciò so quale parte di stringa voglio collegare

gestire il clic sul collegamento di visualizzazione del testo nella mia app Android

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

e utilizzo: linkify (yourTextView, urlString, context)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.