Android TextView với các liên kết có thể nhấp: làm thế nào để nắm bắt các nhấp chuột?


116

Tôi có một TextView đang hiển thị HTML cơ bản, chứa hơn 2 liên kết. Tôi cần ghi lại các nhấp chuột vào các liên kết và mở các liên kết - trong WebView nội bộ của riêng tôi (không phải trong trình duyệt mặc định.)

Phương pháp phổ biến nhất để xử lý kết xuất liên kết có vẻ như sau:

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

Tuy nhiên, điều này khiến các liên kết mở trong trình duyệt web nội bộ mặc định (hiển thị hộp thoại "Hoàn thành Hành động Sử dụng ...").

Tôi đã thử triển khai onClickListener, được kích hoạt đúng cách khi liên kết được nhấp vào, nhưng tôi không biết làm thế nào để xác định LIÊN kết NÀO được nhấp vào ...

text_view.setOnClickListener(new OnClickListener(){

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

});

Ngoài ra, tôi đã thử tạo một lớp LinkMovementMethod tùy chỉnh và triển khai 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;
}

Ý tưởng?


Giải pháp ví dụ

Tôi đã đưa ra một giải pháp phân tích cú pháp các liên kết ra khỏi chuỗi HTML và làm cho chúng có thể nhấp được, sau đó cho phép bạn phản hồi với URL.


1
Tại sao bạn không sử dụng Spannable String. ??
Renjith

1
Trên thực tế, HTML được cung cấp bởi một máy chủ từ xa, không phải do ứng dụng của tôi tạo ra.
Zane Claes

Giải pháp ví dụ của bạn rất hữu ích; bằng cách sử dụng cách tiếp cận đó, tôi nắm bắt các lần nhấp một cách độc đáo và có thể khởi chạy Hoạt động khác, với các thông số, tùy thuộc vào liên kết nào đã được nhấp. (Điểm mấu chốt cần hiểu là "Làm điều gì đó với span.getURL()".) Bạn thậm chí có thể đăng nó như một câu trả lời, vì nó tốt hơn câu trả lời được chấp nhận hiện tại!
Jonik

Câu trả lời:


223

Dựa trên một câu trả lời khác , đây là một hàm setTextViewHTML () phân tích cú pháp các liên kết ra khỏi chuỗi HTML và làm cho chúng có thể nhấp được, sau đó cho phép bạn trả lời 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
Làm việc rất tốt. Với cách tiếp cận này (không giống như các câu trả lời khác), tôi quản lý để 1) nắm bắt các lần nhấp và 2) khởi chạy Hoạt động khác, với các thông số, tùy thuộc vào liên kết nào được nhấp.
Jonik

hoàn hảo ... đã cứu một ngày của tôi
maverickosama92

Tuyệt vời, nhưng nếu bạn áp dụng nó vào một ListView (i có nghĩa là, để TextView nội của mỗi phần tử), làm cho danh sách các nhấp được, mặc dù các liên kết vẫn có thể click
voghDev

@voghDev điều này xảy ra với ListViews khi một View's focusableđược thiết lập đúng. Điều này thường xảy ra với Buttons / ImageButtons. Hãy thử gọi setFocusable(false)của bạn TextView.
Sufian

Hãy chắc chắn để sử dụng text.setMovementMethod(LinkMovementMethod.getInstance());nếu không sử dụng URLSpan
rajath

21

Bạn đã làm như sau:

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

bạn đã thử theo thứ tự ngược lại như hình dưới đây chưa?

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

và không có:

text_view.setLinksClickable(true);

3
Nó hoạt động nhưng không có bất kỳ tín hiệu nào cho thấy người dùng đã nhấp vào liên kết, tôi cần dấu hiệu / hoạt ảnh / đánh dấu khi liên kết được nhấp vào ... tôi phải làm gì?
lightsaber

@StarWars bạn có thể sử dụng (StateList) [ developer.android.com/intl/pt-br/guide/topics/resources/… trong Android thuần túy, nhưng với HTML thì tôi không biết.
ademar111190

20

Điều này có thể được giải quyết đơn giản bằng cách sử dụng Spannable String. Điều bạn thực sự muốn làm (Yêu cầu kinh doanh) hơi không rõ ràng đối với tôi vì vậy mã sau đây sẽ không đưa ra câu trả lời chính xác cho tình huống của bạn nhưng tôi rất chắc chắn rằng nó sẽ cung cấp cho bạn một số ý tưởng và bạn sẽ có thể giải quyết vấn đề của mình dựa trên đoạn mã sau.

Khi bạn làm vậy, tôi cũng nhận được một số dữ liệu qua phản hồi HTTP và tôi đã thêm một số văn bản được gạch chân bổ sung trong trường hợp của tôi là "thêm" và văn bản được gạch chân này sẽ mở trình duyệt web khi nhấp vào sự kiện. Hy vọng điều này sẽ giúp ích cho bạn.

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

Tuyệt quá! Tôi đã sửa đổi điều này cho trường hợp của tôi. Tôi sẽ chỉnh sửa bài đăng của mình để bao gồm mã.
Zane Claes

có một giải pháp tương tự có thể được sử dụng bên trong xml?
nhà phát triển android

Tôi đã làm cho nó hoạt động với phiên bản sửa đổi của OP (trong câu hỏi), không phải với cái này. (Với phiên bản này, các nhấp chuột chuyển thẳng đến hộp thoại "hoàn tất hành động bằng cách sử dụng".)
Jonik

Tôi đã sử dụng logic này, tuy nhiên phải thay thế UnderlineSpan bằng URLSpan. Ngoài ra, cần phải xóa các nhịp cũ khỏi SpannableStringBuilder.
Ray

4
Biến 'd' ở đây là gì?
Salman Khan,

15

Tôi đã gặp vấn đề tương tự nhưng rất nhiều văn bản lẫn lộn với ít liên kết và email. Tôi nghĩ rằng sử dụng 'autoLink' là một cách dễ dàng hơn và rõ ràng hơn để làm điều đó:

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

Bạn có thể đặt Linkify.EMAIL_ADDRESSES hoặc Linkify.WEB_URLS nếu chỉ có một trong số chúng bạn muốn sử dụng hoặc đặt từ bố cục XML

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

Các tùy chọn có sẵn là: không, web, email, điện thoại, bản đồ, tất cả


1
Hi là có cách nào để đánh chặn mục đích bắn vào thời điểm bấm vào một liên kết
Manmohan Soni

1
Đã 6 năm kể từ câu trả lời này .. Tất nhiên nó có thể đã thay đổi trong phiên bản Android mới nhất ^^ Nó không có nghĩa là nó không hoạt động vào thời điểm đó ^^
Jordi

8

Giải pháp

Tôi đã triển khai một lớp học nhỏ với sự trợ giúp mà bạn có thể xử lý các nhấp chuột dài trên chính TextView và các lần nhấn vào các liên kết trong TextView.

Bố trí

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

Sử dụng

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

Liên kết

Hy vọng điều này vô cùng thuận lợi! Bạn có thể tìm thấy mã ở đây .


Vui lòng kiểm tra lại mã của bạn, từ dòng text_view.setMovementMethod(new TextViewClickMovement(this, context));; Android Studio đang phàn nàn rằng contextkhông thể giải quyết được.
X09

Nếu bạn sao chép mã nguồn từ bitbucket, bạn nên thay đổi vị trí của ngữ cảnh và trình nghe như this text_view.setMovementMethod (new TextViewClickMovement (context. This));
Victor Apoyan

Đó sẽ là phân tích cú pháp hai ngữ cảnh cho các tham số. Không hiệu quả thưa ông. Mặc dù câu trả lời được chấp nhận đang hoạt động với tôi bây giờ
X09

Cảm ơn bạn đã trả lời của bạn, thưa ông, một trong những tốt nhất hiện có cho loại này, cảm ơn bạn!
Vulovic Vukasin 14/02/17


6

Tôi đã tạo một chức năng tiện ích mở rộng dễ dàng trong Kotlin để bắt các lần nhấp vào liên kết url trong TextView bằng cách áp dụng một lệnh gọi lại mới cho các phần tử URLSpan.

string.xml (liên kết ví dụ trong văn bản)

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

Đảm bảo rằng văn bản được mở rộng của bạn được đặt thành TextView trước khi bạn gọi "handleUrlClicks"

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

Đây là chức năng mở rộng:

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

Đây là cách tôi gọi nó:

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

5

Nếu bạn đang sử dụng Kotlin, tôi đã viết một tiện ích mở rộng đơn giản cho trường hợp này:

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

Sử dụng:

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

1

Một cách tiếp cận đơn giản hơn, đơn giản hơn (dành cho những nhà phát triển lười biếng như tôi;)

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 
}

Nếu được yêu cầu, bạn cũng có thể kiểm tra lược đồ / kiểu mimetype của ý định


0

Tôi chỉ sử dụng textView và thiết lập khoảng cách cho url và xử lý nhấp chuột.

Tôi đã tìm thấy giải pháp rất thanh lịch ở đây, không có liên kết - theo đó tôi biết phần nào của chuỗi tôi muốn liên kết

xử lý nhấp chuột vào liên kết textview trong ứng dụng Android của tôi

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

và cách sử dụng: linkify (yourTextView, urlString, context)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.