154 votes

TextView Android avec liens cliquables : comment capturer les clics ?

J'ai un TextView qui rend du HTML de base, contenant plus de 2 liens. J'ai besoin de capturer les clics sur les liens et d'ouvrir les liens - dans ma propre WebView interne (pas dans le navigateur par défaut).

La méthode la plus courante pour gérer le rendu des liens semble être la suivante :

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

Cependant, les liens s'ouvrent alors dans le navigateur Web interne par défaut (avec la boîte de dialogue "Complete Action Using...").

J'ai essayé d'implémenter un onClickListener, qui se déclenche correctement lorsque le lien est cliqué, mais je ne sais pas comment déterminer QUEL lien a été cliqué...

text_view.setOnClickListener(new OnClickListener(){

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

});

Sinon, j'ai essayé de créer une classe personnalisée LinkMovementMethod et d'implémenter 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;
}

Des idées ?


Exemple de solution

Je suis venu avec une solution qui analyse les liens à partir d'une chaîne HTML et les rend cliquables, puis vous permet de répondre à l'URL.

270voto

Zane Claes Points 4001

Sur la base de une autre réponse Dans le cas d'un site Web, voici une fonction setTextViewHTML() qui analyse les liens à partir d'une chaîne HTML et les rend cliquables, puis vous permet de répondre à l'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());       
}

49voto

nastylion Points 626

J'ai créé une fonction d'extension simple dans Kotlin pour attraper les clics de liens url dans un TextView en appliquant un nouveau callback aux éléments URLSpan.

strings.xml (exemple de lien dans le texte)

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

Assurez-vous que votre texte étendu est placé dans le TextView avant d'appeler "handleUrlClicks".

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

Il s'agit de la fonction d'extension :

/**
 * 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()
}

C'est comme ça que je l'appelle :

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

25voto

ademar111190 Points 2855

Vous avez fait comme suit :

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

Avez-vous essayé dans l'ordre inverse comme indiqué ci-dessous ?

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

et sans :

text_view.setLinksClickable(true);

23voto

kaluwila Points 1179

Ce que vous voulez vraiment faire (exigence commerciale) n'est pas très clair pour moi, donc le code suivant ne donnera pas de réponse exacte à votre situation, mais je suis sûr qu'il vous donnera une idée et que vous serez en mesure de résoudre votre problème sur la base du code suivant.

Comme vous le faites, je reçois également des données via la réponse HTTP et j'ai ajouté quelques données supplémentaires. souligné texte dans mon cas "plus" et ce texte souligné ouvrira le navigateur Web lors d'un événement de clic.

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

18voto

Jordi Points 566

J'ai eu le même problème mais avec beaucoup de texte mélangé à quelques liens et emails. Je pense que l'utilisation de "autoLink" est un moyen plus simple et plus propre de le faire :

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

Vous pouvez définir Linkify.EMAIL_ADDRESSES ou Linkify.WEB_URLS s'il n'y a qu'un seul d'entre eux que vous souhaitez utiliser ou définir à partir de la mise en page XML

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

Les options disponibles sont les suivantes : aucun, web, email, téléphone, carte, tous

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X