Android TextView з клацаючими посиланнями: як зафіксувати кліки?


117

У мене TextView, що рендерує базовий HTML, містить 2+ посилань. Мені потрібно зафіксувати кліки посилання та відкрити посилання - у власному внутрішньому веб-перегляді (не в браузері за замовчуванням.)

Найпоширеніший метод обробки візуалізації посилань виглядає таким чином:

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

Однак це призводить до відкриття посилань у внутрішньому веб-браузері за замовчуванням (показує діалогове вікно "Завершити дію за допомогою ...").

Я спробував реалізувати onClickListener, який належним чином спрацьовує при натисканні на посилання, але я не знаю, як визначити, на яке посилання було натиснуто ...

text_view.setOnClickListener(new OnClickListener(){

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

});

Крім того, я спробував створити користувацький клас 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;
}

Ідеї?


Приклад рішення

Я придумав рішення, яке розбирає посилання з рядка HTML і робить їх доступними для натискання, а потім дозволяє відповідати на URL-адресу.


1
Чому ви не використовуєте Spannable String?
Renjith

1
Насправді HTML надається віддаленим сервером, а не генерується моєю програмою.
Зейн Клайз

Ваш приклад рішення дуже корисний; використовуючи такий підхід, я добре фіксую кліки і можу запустити ще одну діяльність, з параметрами, залежно від того, на яке посилання було натиснуто. (Ключовим моментом, щоб зрозуміти було «Зроби щось з span.getURL()».) Ви навіть можете опублікувати це як відповідь, оскільки це краще, ніж відповідь, що прийнята зараз!
Jonik

Відповіді:


223

На підставі іншої відповіді , ось функція 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());       
}

5
Працювали чудово. При такому підході (на відміну від інших відповідей) мені вдалося 1) зафіксувати кліки та 2) запустити ще одну діяльність, з параметрами, залежно від того, на яке посилання було натиснуто.
Jonik

ідеально ... врятував мій день
maverickosama92

Чудово, але якщо ви застосуєте його до ListView (я маю на увазі до внутрішнього TextView кожного елемента), робить список не натискаючим, хоча посилання все ще можна натискати
voghDev

@voghDev це відбувається з ListViewз , коли View«s focusableвстановлено вірно. Зазвичай це відбувається з Buttons / ImageButtons. Спробуйте зателефонувати setFocusable(false)на своє TextView.
Суфіан

Переконайтеся , що використання , text.setMovementMethod(LinkMovementMethod.getInstance());якщо не використовується URLSpan
rajath

21

Ви зробили наступне:

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

ви пробували у зворотному порядку, як показано нижче?

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

і без:

text_view.setLinksClickable(true);

3
Це працює, але без жодного сигналу про те, що користувач натиснув посилання, мені потрібна кия / анімація / виділення, коли натискається посилання ... що мені робити?
світловий меч

@StarWars ви можете використовувати (StateList) [ developer.android.com/intl/pt-br/guide/topics/resources/… в чистому андроїді, але з HTML я не знаю.
ademar111190

20

Це можна вирішити просто за допомогою Spannable String. Що ви дійсно хочете зробити (Бізнес-вимога) для мене трохи незрозуміло, тому наступний код не дасть точної відповіді на вашу ситуацію, але я певно впевнений, що він дасть вам деяке уявлення і ви зможете вирішити свою проблему на основі наступного коду.

Як і ви, я також отримую деякі дані через відповідь 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());

Чудово! Я змінив це для свого випадку. Я відредагую свою публікацію, щоб включити код.
Зейн Клайс

чи є подібне рішення, яке можна використовувати всередині xml?
андроїд розробник

Я отримав це, працюючи з модифікованою версією OP (у питанні), а не з цією. (З цією версією кліки перейшли безпосередньо до діалогу "завершення дії за допомогою".)
Jonik

Я використовував цю логіку, проте довелося замінити UnderlineSpan на URLSpan. Також потрібно було видалити старі прольоти зі SpannableStringBuilder.
Промінь

4
Що тут змінної 'd'?
Салман Хан

15

У мене була така ж проблема, але багато тексту змішано з кількома посиланнями та електронними листами. Я думаю, що використання "autoLink" - це простіший і чистіший спосіб зробити це:

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

Ви можете встановити Linkify.EMAIL_ADDRESSES або Linkify.WEB_URLS, якщо з макета XML є лише один із них, який ви хочете використовувати або встановити

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

Доступні варіанти: жоден, веб, електронна пошта, телефон, карта, усі


1
Привіт, чи є якийсь спосіб перехопити наміри, вистрілені під час натискання посилання
Manmohan Soni

1
З цієї відповіді минуло 6 років. Звичайно, це можливо змінилося в останній версії Android ^^ Це не означає, що тоді вона не спрацювала ^^
Джорді

8

Рішення

Я реалізував невеликий клас, за допомогою якого ви можете обробляти довгі кліки по самому TextView і Taps посилання на TextView.

Макет

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

Використання

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

Посилання

Сподіваюсь, це helops! Ви можете знайти код тут .


Перевірте свій код ще раз із рядка text_view.setMovementMethod(new TextViewClickMovement(this, context));; Android Studio скаржиться, що contextне вдалося вирішити.
X09

Якщо ви копіюєте вихідний код з бітбукета, вам слід змінити місце контексту та слухача, як цей text_view.setMovementMethod (новий TextViewClickMovement (контекст. Це));
Віктор Апоян

Це буде аналіз двох параметрів для параметрів. Сер не працював. Хоча прийнята відповідь працює для мене зараз
X09

Дякую за Вашу відповідь, сер, найкращий для такого роду, дякую!
Вулович Вукасін


7

Я зробив просту функцію розширення в Котліні, щоб зафіксувати кліки посилання на URL у TextView, застосувавши новий зворотний виклик до елементів URLSpan.

strings.xml (приклад посилання в тексті)

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

Переконайтеся, що ваш розгорнутий текст встановлений на TextView, перш ніж викликати "handleUrlClicks"

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

Це функція розширення:

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

Ось як я це називаю:

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

Дивовижно! _______
Валентин Юр'єв

5

Якщо ви використовуєте 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
}

Використання:

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

1

Альтернативний, не дуже простий підхід (для ледачих розробників, як я;)

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 
}

Якщо потрібно, ви також можете перевірити схему / міметик наміру


0

Я використовую лише textView і встановіть проміжок для URL-адреси та клацання обробкою.

Я знайшов тут дуже елегантне рішення, без linkify - відповідно до цього я знаю, яку частину струни я хочу пов’язати

обробляти текстове перегляд посилання клацніть у моєму додатку для Android

в котліні:

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

та використання: linkify (yourTextView, urlString, контекст)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.