Android TextView dengan Link yang Dapat Diklik: bagaimana cara menangkap klik?

116

Saya memiliki TextView yang merender HTML biasa, yang berisi 2+ tautan. Saya perlu menangkap klik pada tautan dan membuka tautan - di WebView internal saya sendiri (bukan di browser default.)

Metode paling umum untuk menangani perenderan tautan tampaknya seperti ini:

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

Namun, ini menyebabkan tautan terbuka di browser web internal default (menampilkan dialog "Selesaikan Tindakan Menggunakan ...").

Saya mencoba menerapkan onClickListener, yang dipicu dengan benar saat tautan diklik, tetapi saya tidak tahu cara menentukan tautan mana yang diklik ...

text_view.setOnClickListener(new OnClickListener(){

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

});

Atau, saya mencoba membuat kelas LinkMovementMethod khusus dan menerapkan 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;
}

Ide ide?


Contoh solusi

Saya datang dengan solusi yang mem-parsing tautan dari string HTML dan membuatnya dapat diklik, dan kemudian memungkinkan Anda merespons URL.

Zane Claes
sumber
1
Mengapa Anda tidak menggunakan Spannable String. ??
Renjith
1
Pada kenyataannya, HTML disediakan oleh server jarak jauh, bukan dibuat oleh aplikasi saya.
Zane Claes
Solusi contoh Anda sangat membantu; menggunakan pendekatan itu saya menangkap klik dengan baik dan dapat meluncurkan Aktivitas lain, dengan parameter, bergantung pada tautan mana yang diklik. (Poin kunci untuk dipahami adalah "Lakukan sesuatu dengan span.getURL()".) Anda bahkan dapat mempostingnya sebagai jawaban, karena itu lebih baik daripada jawaban yang diterima saat ini!
Jonik

Jawaban:

223

Berdasarkan jawaban lain , berikut adalah fungsi setTextViewHTML () yang mem-parsing tautan dari string HTML dan membuatnya dapat diklik, lalu memungkinkan Anda merespons 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());       
}
Zane Claes
sumber
5
Bekerja dengan baik. Dengan pendekatan ini (tidak seperti jawaban lain), saya berhasil 1) menangkap klik dan 2) meluncurkan Aktivitas lain, dengan parameter, tergantung pada tautan mana yang diklik.
Jonik
sempurna ... menyelamatkan hariku
maverickosama92
Luar biasa, tetapi jika Anda menerapkannya ke ListView (maksud saya, untuk setiap TextView bagian dalam elemen), membuat daftar tidak dapat diklik, meskipun tautan masih dapat diklik
voghDev
@voghDev ini terjadi dengan ListViews ketika View's focusablediatur benar. Ini biasanya terjadi dengan Buttons / ImageButtons. Coba hubungi setFocusable(false)Anda TextView.
Sufian
Pastikan untuk menggunakan text.setMovementMethod(LinkMovementMethod.getInstance());jika tidak menggunakan URLSpan
rajath
21

Anda telah melakukan sebagai berikut:

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

Sudahkah Anda mencoba dalam urutan terbalik seperti yang ditunjukkan di bawah ini?

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

dan tanpa:

text_view.setLinksClickable(true);
ademar111190
sumber
3
Ini berfungsi tetapi tanpa sinyal bahwa pengguna mengklik tautan, saya memerlukan isyarat / animasi / sorotan ketika tautan diklik ... apa yang harus saya lakukan?
lightsaber
@StarWars Anda dapat menggunakan (StateList) [ developer.android.com/intl/pt-br/guide/topics/resources/… di android murni, tetapi dengan HTML saya tidak tahu.
ademar111190
20

Ini dapat diselesaikan dengan mudah dengan menggunakan Spannable String. Apa yang benar-benar ingin Anda lakukan (Persyaratan Bisnis) sedikit tidak jelas bagi saya sehingga kode berikut tidak akan memberikan jawaban yang tepat untuk situasi Anda, tetapi saya yakin kecil itu akan memberi Anda beberapa ide dan Anda akan dapat menyelesaikan masalah Anda berdasarkan kode berikut.

Saat Anda melakukannya, saya juga mendapatkan beberapa data melalui respons HTTP dan saya telah menambahkan beberapa teks bergaris bawah tambahan dalam kasus saya "lebih" dan teks yang digarisbawahi ini akan membuka browser web saat acara diklik. Semoga ini bisa membantu Anda.

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
sumber
Bagus! Saya telah memodifikasi ini untuk kasus saya. Saya akan mengedit posting saya untuk memasukkan kode.
Zane Claes
apakah ada solusi serupa yang dapat digunakan di dalam xml?
Pengembang android
Saya mendapatkannya bekerja dengan versi modifikasi OP (dalam pertanyaan), bukan dengan ini. (Dengan versi ini, klik langsung menuju ke dialog "selesaikan tindakan menggunakan".)
Jonik
Saya menggunakan logika ini, namun harus mengganti UnderlineSpan dengan URLSpan. Juga diperlukan untuk menghapus span lama dari SpannableStringBuilder.
Ray
4
Apa variabel 'd' di sini?
Salman Khan
15

Saya memiliki masalah yang sama tetapi banyak teks bercampur dengan beberapa tautan dan email. Menurut saya menggunakan 'tautan otomatis' adalah cara yang lebih mudah dan lebih bersih untuk melakukannya:

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

Anda dapat menyetel Linkify.EMAIL_ADDRESSES atau Linkify.WEB_URLS jika hanya ada satu yang ingin Anda gunakan atau setel dari tata letak XML

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

Opsi yang tersedia adalah: tidak ada, web, email, telepon, peta, semua

Jordi
sumber
1
Hai, apakah ada cara untuk menghalangi maksud yang ditembakkan pada saat mengklik tautan
Manmohan Soni
1
Sudah 6 tahun dari jawaban ini .. Tentu saja itu mungkin telah berubah di versi Android terbaru ^^ Itu tidak berarti itu tidak berfungsi saat itu ^^
Jordi
8

Larutan

Saya telah menerapkan kelas kecil dengan bantuan yang Anda dapat menangani klik panjang pada TextView itu sendiri dan Taps pada tautan di TextView.

Tata Letak

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

Pemakaian

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

Tautan

Semoga helops ini! Anda dapat menemukan kode di sini .

Victor Apoyan
sumber
Harap periksa kembali kode Anda, dari baris text_view.setMovementMethod(new TextViewClickMovement(this, context));; Android Studio mengeluh karena contexttidak dapat diselesaikan.
X09
Jika Anda menyalin kode sumber dari bitbucket, Anda harus mengubah tempat konteks dan pendengar seperti ini text_view.setMovementMethod (baru TextViewClickMovement (konteks. Ini));
Victor Apoyan
Itu akan mem-parsing dua konteks untuk parameter. Tidak berhasil, Pak. Meskipun jawaban yang diterima bekerja untuk saya sekarang
X09
Terima kasih atas jawaban Anda Pak, yang terbaik di luar sana untuk jenis ini, terima kasih!
Vulovic Vukasin
8

Solusi yang lebih bersih dan lebih baik, menggunakan pustaka Linkify asli .

Contoh:

Linkify.addLinks(mTextView, Linkify.ALL);
Fidel Montesino
sumber
1
Apakah mungkin untuk mengganti acara onClick menggunakan ini?
Milind Mevada
6

Saya membuat fungsi ekstensi yang mudah di Kotlin untuk menangkap klik tautan url di TextView dengan menerapkan callback baru ke elemen URLSpan.

strings.xml (contoh tautan dalam teks)

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

Pastikan teks span Anda disetel ke TextView sebelum Anda memanggil "handleUrlClicks"

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

Ini adalah fungsi ekstensi:

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

Beginilah saya menyebutnya:

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

Jika Anda menggunakan Kotlin, saya menulis ekstensi sederhana untuk kasus ini:

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

Pemakaian:

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

Alternatif, cara pendekatan yang lebih sederhana (untuk pengembang yang malas seperti saya;)

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 
}

Jika perlu, Anda juga bisa memeriksa skema / mimetype maksud

pengguna1806772
sumber
0

Saya hanya menggunakan textView, dan mengatur rentang untuk url dan menangani klik.

Saya menemukan solusi yang sangat elegan di sini, tanpa tautan - menurut itu saya tahu bagian mana dari string yang ingin saya tautkan

menangani klik tautan textview di aplikasi android saya

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

dan penggunaan: linkify (yourTextView, urlString, konteks)

Peter
sumber