Android TextView mit anklickbaren Links: Wie werden Klicks erfasst?

116

Ich habe eine TextView, die einfaches HTML rendert und 2+ Links enthält. Ich muss Klicks auf die Links erfassen und die Links öffnen - in meinem eigenen internen WebView (nicht im Standardbrowser).

Die gebräuchlichste Methode zum Rendern von Links scheint folgende zu sein:

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

Dies führt jedoch dazu, dass die Links im internen Standard-Webbrowser geöffnet werden (wobei das Dialogfeld "Aktion mit ... abschließen" angezeigt wird).

Ich habe versucht, einen onClickListener zu implementieren, der beim Klicken auf den Link ordnungsgemäß ausgelöst wird, aber ich weiß nicht, wie ich feststellen kann, auf welchen Link geklickt wurde ...

text_view.setOnClickListener(new OnClickListener(){

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

});

Alternativ habe ich versucht, eine benutzerdefinierte LinkMovementMethod-Klasse zu erstellen und onTouchEvent zu implementieren ...

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

Ideen?


Beispiellösung

Ich habe eine Lösung gefunden, die die Links aus einer HTML-Zeichenfolge analysiert und anklickbar macht und Sie dann auf die URL antworten lässt.

Zane Claes
quelle
1
Warum verwenden Sie nicht Spannable String?
Renjith
1
In Wirklichkeit wird der HTML-Code von einem Remote-Server bereitgestellt, der nicht von meiner Anwendung generiert wurde.
Zane Claes
Ihre Beispiellösung ist sehr hilfreich. Mit diesem Ansatz fange ich Klicks gut ein und kann eine weitere Aktivität mit Parametern starten, je nachdem, auf welchen Link geklickt wurde. (Der wichtigste Punkt zum Verstehen war "Mach etwas mit span.getURL()".) Du könntest es sogar als Antwort posten, da es besser ist als die derzeit akzeptierte Antwort!
Jonik

Antworten:

223

Basierend auf einer anderen Antwort ist hier eine Funktion setTextViewHTML (), die die Links aus einer HTML-Zeichenfolge analysiert und anklickbar macht und Sie dann auf die URL antworten lässt.

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());       
}
Zane Claes
quelle
5
Hat super funktioniert. Mit diesem Ansatz gelang es mir (im Gegensatz zu den anderen Antworten), 1) Klicks zu erfassen und 2) eine weitere Aktivität mit Parametern zu starten, je nachdem, auf welchen Link geklickt wurde.
Jonik
perfekt ... rettete meinen Tag
maverickosama92
Wunderbar, aber wenn Sie es auf eine ListView anwenden (ich meine auf die innere TextView jedes Elements), kann die Liste nicht angeklickt werden, obwohl Links immer noch anklickbar sind
voghDev
@voghDev Dies passiert mit ListViews, wenn a View's focusableauf true gesetzt ist. Dies geschieht normalerweise mit Buttons / ImageButtons. Rufen setFocusable(false)Sie an TextView.
Sufian
text.setMovementMethod(LinkMovementMethod.getInstance());Stellen Sie sicher, dass Sie URLSpan
Rajath
21

Sie haben Folgendes getan:

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

Haben Sie es in umgekehrter Reihenfolge versucht, wie unten gezeigt?

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

und ohne:

text_view.setLinksClickable(true);
ademar111190
quelle
3
Es funktioniert, aber ohne ein Signal, dass der Benutzer auf den Link geklickt hat, brauche ich einen Hinweis / eine Animation / ein Highlight, wenn auf den Link geklickt wird ... was soll ich tun?
Lichtschwert
@StarWars können Sie (StateList) [ developer.android.com/intl/pt-br/guide/topics/resources/… in reinem Android verwenden, aber mit HTML weiß ich nicht.
ademar111190
20

Dies kann einfach mithilfe von Spannable String gelöst werden. Was Sie wirklich tun möchten (Geschäftsanforderung), ist mir ein wenig unklar, sodass der folgende Code keine genaue Antwort auf Ihre Situation gibt, aber ich bin mir ziemlich sicher, dass er Ihnen eine Idee gibt und Sie können Ihr Problem anhand des folgenden Codes lösen.

Während Sie dies tun, erhalte ich auch einige Daten über eine HTTP-Antwort und ich habe in meinem Fall "mehr" zusätzlichen unterstrichenen Text hinzugefügt. Dieser unterstrichene Text öffnet den Webbrowser beim Klickereignis. Hoffentlich hilft Ihnen dies.

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());
Dinesh Anuruddha
quelle
Toll! Ich habe dies für meinen Fall geändert. Ich werde meinen Beitrag so bearbeiten, dass er den Code enthält.
Zane Claes
Gibt es eine ähnliche Lösung, die in XML verwendet werden kann?
Android-Entwickler
Ich habe es mit der modifizierten Version von OP (in der Frage) zum Laufen gebracht, nicht mit dieser. (Mit dieser Version gingen die Klicks direkt zum Dialogfeld "Aktion mit" abschließen.)
Jonik
Ich habe diese Logik verwendet, musste jedoch den UnderlineSpan durch URLSpan ersetzen. Außerdem musste ich die alten Bereiche aus dem SpannableStringBuilder entfernen.
Ray
4
Was ist hier die Variable 'd'?
Salman Khan
15

Ich hatte das gleiche Problem, aber viel Text gemischt mit wenigen Links und E-Mails. Ich denke, die Verwendung von 'autoLink' ist eine einfachere und sauberere Möglichkeit:

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

Sie können Linkify.EMAIL_ADDRESSES oder Linkify.WEB_URLS festlegen, wenn Sie nur eine davon verwenden oder im XML-Layout festlegen möchten

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

Die verfügbaren Optionen sind: keine, Web, E-Mail, Telefon, Karte, alle

Jordi
quelle
1
Hallo, gibt es eine Möglichkeit, die Absicht abzufangen, die zum Zeitpunkt des Klickens eines Links ausgelöst wurde
Manmohan Soni
1
Es ist 6 Jahre her von dieser Antwort. Natürlich hat es sich in der neuesten Android-Version geändert ^^ Es bedeutet nicht, dass es damals nicht funktioniert hat ^^
Jordi
8

Lösung

Ich habe eine kleine Klasse implementiert, mit deren Hilfe Sie lange Klicks auf TextView selbst und Tippen auf die Links in TextView verarbeiten können.

Layout

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 "";
        }
    }
}

Verwendung

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

Links

Hoffe das hilft! Code finden Sie hier .

Victor Apoyan
quelle
Bitte überprüfen Sie Ihren Code erneut in der Zeile text_view.setMovementMethod(new TextViewClickMovement(this, context));. Android Studio beschwert sich, dass contextnicht gelöst werden konnte.
X09
Wenn Sie den Quellcode von bitbucket kopieren, sollten Sie den Ort des Kontexts und des Listeners wie folgt ändern: text_view.setMovementMethod (new TextViewClickMovement (context. This));
Victor Apoyan
Das wird zwei Kontexte für die Parameter analysieren. Hat nicht funktioniert, Sir. Obwohl die akzeptierte Antwort jetzt für mich funktioniert
X09
Vielen Dank für Ihre Antwort, Sir, die beste für diese Art, danke!
Vulovic Vukasin
8

Eine sauberere und bessere Lösung mit der Linkify-Bibliothek von native .

Beispiel:

Linkify.addLinks(mTextView, Linkify.ALL);
Fidel Montesino
quelle
1
Ist es möglich, ein onClick-Ereignis damit zu überschreiben?
Milind Mevada
6

Ich habe in Kotlin eine einfache Erweiterungsfunktion erstellt, mit der URL-Link-Klicks in einer Textansicht abgefangen werden können, indem ein neuer Rückruf auf URLSpan-Elemente angewendet wird.

string.xml (Beispiellink im Text)

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

Stellen Sie sicher, dass Ihr übergreifender Text auf TextView eingestellt ist, bevor Sie "handleUrlClicks" aufrufen.

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

Dies ist die Erweiterungsfunktion:

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

So nenne ich es:

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

Wenn Sie Kotlin verwenden, habe ich eine einfache Erweiterung für diesen Fall geschrieben:

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

Verwendung:

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

Ein alternativer, imho viel einfacherer Ansatz (für faule Entwickler wie mich;)

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 
}

Bei Bedarf können Sie auch nach Schema / Mimetyp der Absicht suchen

user1806772
quelle
0

Ich benutze nur textView und setze die Spanne für URL und Handle Click.

Ich habe hier eine sehr elegante Lösung gefunden, ohne zu verknüpfen - demnach weiß ich, welchen Teil der Zeichenfolge ich verknüpfen möchte

Text Textlink Link klicken in meiner Android App

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

und Verwendung: linkify (yourTextView, urlString, context)

Peter
quelle