带有可点击链接的 Android TextView:如何捕获点击?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12418279/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 10:36:32  来源:igfitidea点击:

Android TextView with Clickable Links: how to capture clicks?

androidhyperlinktextview

提问by Zane Claes

I have a TextView which is rendering basic HTML, containing 2+ links. I need to capture clicks on the links and open the links -- in my own internal WebView (not in the default browser.)

我有一个呈现基本 HTML 的 TextView,其中包含 2 个以上的链接。我需要捕获链接上的点击并打开链接——在我自己的内部 WebView 中(而不是在默认浏览器中。)

The most common method to handle link rendering seems to be like this:

处理链接渲染的最常见方法似乎是这样的:

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

However, this causes the links to open in the default internal web browser (showing the "Complete Action Using..." dialog).

但是,这会导致链接在默认的内部 Web 浏览器中打开(显示“使用...完成操作”对话框)。

I tried implementing a onClickListener, which properly gets triggered when the link is clicked, but I don't know how to determine WHICH link was clicked...

我尝试实现一个 onClickListener,当点击链接时它会正确触发,但我不知道如何确定点击了哪个链接......

text_view.setOnClickListener(new OnClickListener(){

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

});

Alternatively, I tried creating a custom LinkMovementMethod class and implementing onTouchEvent...

或者,我尝试创建一个自定义 LinkMovementMethod 类并实现 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?

想法?



Example solution

示例解决方案

I came up with a solutionwhich parses the links out of a HTML string and makes them clickable, and then lets you respond to the URL.

我想出了一个解决方案,它解析 HTML 字符串中的链接并使它们可点击,然后让您响应 URL。

回答by Zane Claes

Based upon another answer, here's a function setTextViewHTML() which parses the links out of a HTML string and makes them clickable, and then lets you respond to the URL.

根据另一个答案,这里有一个函数 setTextViewHTML() ,它解析 HTML 字符串中的链接并使它们可点击,然后让您响应 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());       
}

回答by Dinesh Anuruddha

This can be simply solved by using Spannable String.What you really want to do (Business Requirement) is little bit unclear to me so following code will not give exact answer to your situation but i am petty sure that it will give you some idea and you will be able to solve your problem based on the following code.

这可以通过使用 Spannable String 来简单解决。你真正想要做什么(业务需求)对我来说有点不清楚,所以下面的代码不会对你的情况给出确切的答案,但我很确定它会给你一些想法和您将能够根据以下代码解决您的问题。

As you do, i'm also getting some data via HTTP response and i have added some additional underlinedtext in my case "more"and this underlined text will open the web browser on click event.Hope this will help you.

正如您所做的那样,我也通过 HTTP 响应获取了一些数据,并且在我的案例“更多”中添加了一些额外的带下划线的文本,这个带下划线的文本将在单击事件时打开网络浏览器。希望这对您有所帮助。

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

回答by ademar111190

You've done as follows:

您已完成以下操作:

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

have you tried in reverse order as shown below?

您是否尝试过如下所示的相反顺序?

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

and without:

并且没有:

text_view.setLinksClickable(true);

回答by Jordi

I've had the same problem but a lot of text mixed with few links and emails. I think using 'autoLink' is a easier and cleaner way to do it:

我遇到了同样的问题,但大量文本与少量链接和电子邮件混合在一起。我认为使用“自动链接”是一种更简单、更简洁的方法:

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

You can set Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS if there's only one of them you want to use or set from the XML layout

您可以设置 Linkify.EMAIL_ADDRESSES 或 Linkify.WEB_URLS 如果您只想使用或从 XML 布局设置其中之一

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

The available options are: none, web, email, phone, map, all

可用选项有:无、网络、电子邮件、电话、地图、全部

回答by Fidel Montesino

A way cleaner and better solution, using native's Linkify library.

一种更清洁、更好的解决方案,使用本机的Linkify 库

Example:

例子:

Linkify.addLinks(mTextView, Linkify.ALL);

回答by Victor Apoyan

Solution

解决方案

I have implemented a small class with the help of which you can handle long clicks on TextView itself and Taps on the links in the TextView.

我已经实现了一个小类,在它的帮助下,您可以处理对 TextView 本身的长时间点击和点击 TextView 中的链接。

Layout

布局

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

TextViewClickMovement.java

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

Usage

用法

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

链接

Hope this helops! You can find code here.

希望这会有所帮助!您可以在此处找到代码。

回答by nastylion

I made an easy extension function in Kotlinto catch url link clicks in a TextView by applying a new callback to URLSpan elements.

我在Kotlin 中创建了一个简单的扩展函数,通过对 URLSpan 元素应用新的回调来捕获 TextView 中的 url 链接点击。

strings.xml (example link in text)

strings.xml(文本中的示例链接)

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

Make sure your spanned text is set to the TextView before you call "handleUrlClicks"

在调用“handleUrlClicks”之前,请确保将跨区文本设置为 TextView

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

This is the extension function:

这是扩展功能:

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

This is how I call it:

我是这样称呼它的:

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

回答by Phil

If you're using Kotlin, I wrote a simple extensionfor this case:

如果您使用的是 Kotlin,我为这种情况编写了一个简单的扩展

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

Usage:

用法:

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

回答by Peter

Im using only textView, and set span for url and handle click.

我只使用 textView,并为 url 设置跨度并处理点击。

I found very elegant solution here, without linkify - according to that I know which part of string I want to linkify

我在这里找到了非常优雅的解决方案,没有链接 - 据此我知道我想链接字符串的哪一部分

handle textview link click in my android app

在我的 android 应用程序中处理 textview 链接单击

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

and usage: linkify(yourTextView, urlString, context)

和用法:linkify(yourTextView, urlString, context)