user3744384_
user3744384_

Reputation: 337

Chrome custom tabs and textview

I have a text view with a link inside it. In code I call the setMovementMethod to open the link when the user clicks on the text. But it opens it in the default browser or the browser chooser.

How can I use chrome custom tabs with a clickable textview?

Upvotes: 6

Views: 2747

Answers (3)

theimpulson
theimpulson

Reputation: 101

Here's a kotlin variant based on Nikola's answer. I also took the liberty of adding URL handling in Chrome Custom Tab:

import android.graphics.Rect
import android.net.Uri
import android.os.Parcel
import android.text.Spannable
import android.text.Spanned
import android.text.method.TransformationMethod
import android.text.style.URLSpan
import android.text.util.Linkify
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.browser.customtabs.CustomTabsIntent

class LinkTransformationMethod : TransformationMethod {

    private val TAG = LinkTransformationMethod::class.java.simpleName

    inner class CustomTabsURLSpan : URLSpan {
        constructor(url: String?) : super(url)
        constructor(src: Parcel?) : super(src!!)

        override fun onClick(widget: View) {
            try {
                CustomTabsIntent.Builder()
                    .build()
                    .launchUrl(widget.context, Uri.parse(url))
            } catch (exception: Exception) {
                Log.e(TAG, "Failed to open link in custom tab!", exception)
                super.onClick(widget)
            }
        }
    }

    override fun getTransformation(source: CharSequence?, view: View?): CharSequence? {
        if (view is TextView) {
            Linkify.addLinks(view, Linkify.WEB_URLS)
            if (view.text == null || view.text !is Spannable) {
                return source
            }
            val text = view.text as Spannable
            val spans = text.getSpans(0, view.length(), URLSpan::class.java)
            spans.indices.reversed().forEach {
                val oldSpan = spans[it]
                val start = text.getSpanStart(oldSpan)
                val end = text.getSpanEnd(oldSpan)
                val url = oldSpan.url
                text.removeSpan(oldSpan)
                text.setSpan(CustomTabsURLSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
            }
            return text
        }
        return source
    }

    override fun onFocusChanged(
        view: View?,
        sourceText: CharSequence?,
        focused: Boolean,
        direction: Int,
        previouslyFocusedRect: Rect?
    ) {}
}

Upvotes: 0

Boy
Boy

Reputation: 7519

I've altered Nikola's answer a bit so that it still will work for other linkify options too.

I've added Patterns.WEB_URL.matcher() and a constructor to set which linkify options you want.

Usage:

textView.setTransformationMethod(new LinkTransformationMethod(Linkify.WEB_URLS | 
    Linkify.EMAIL_ADDRESSES | 
    Linkify.PHONE_NUMBERS));

The complete class itself:

public class LinkTransformationMethod implements TransformationMethod {

    private final int linkifyOptions;

    public LinkTransformationMethod(int linkifyOptions) {
        this.linkifyOptions = linkifyOptions;
    }

    @Override
    public CharSequence getTransformation(CharSequence source, View view) {
        if (view instanceof TextView) {
            TextView textView = (TextView) view;
            Linkify.addLinks(textView, linkifyOptions);
            if (textView.getText() == null || !(textView.getText() instanceof Spannable)) {
                return source;
            }
            Spannable text = (Spannable) textView.getText();
            URLSpan[] spans = text.getSpans(0, textView.length(), URLSpan.class);
            for (int i = spans.length - 1; i >= 0; i--) {
                URLSpan oldSpan = spans[i];
                int start = text.getSpanStart(oldSpan);
                int end = text.getSpanEnd(oldSpan);
                String url = oldSpan.getURL();
                if (!Patterns.WEB_URL.matcher(url).matches()) {
                    continue;
                }
                text.removeSpan(oldSpan);
                text.setSpan(new ChromeTabsUrlSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            return text;
        }
        return source;
    }

    @Override
    public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction, Rect previouslyFocusedRect) {

    }
}

Upvotes: 1

Nikola Despotoski
Nikola Despotoski

Reputation: 50578

This is because the TextView creates URLSpan which is ClickableSpan for each link text pattern. Once the MovementMethod finds url it calls onClick method of the URLSpan. This event starts the ACTION_VIEW intent, that's why you see the default browser starting instead.

What you could do is write your own implementation of the URLSpan, where you'd override onClick method and start the CustomTabs service from there.

First create custom URLSpan that will override onClick method:

public class CustomTabsURLSpan extends URLSpan {
    public CustomTabsURLSpan(String url) {
        super(url);
    }

    public CustomTabsURLSpan(Parcel src) {
        super(src);
    }

    @Override
    public void onClick(View widget) {
       String url = getUrl();
       //attempt to open in CustomTabs, if that fails call super.onClick(widget);
    }
}

Create custom transformation method, that will set spans to the links:

public class LinkTransformationMethod implements TransformationMethod {

    @Override
    public CharSequence getTransformation(CharSequence source, View view) {
        if (view instanceof TextView) {
            TextView textView = (TextView) view;
            Linkify.addLinks(textView, Linkify.WEB_URLS);
            String stringText = textView.getText().toString();
            Spannable text = (Spannable) textView.getText();
            URLSpan[] spans = text.getSpans(0, textView.length(), URLSpan.class);
            for (int i = spans.length - 1; i >= 0; i--) {
                URLSpan oldSpan = spans[i];
                text.removeSpan(oldSpan);
                String url = oldSpan.getURL();
                int startIndex = stringText.indexOf(url);
                int lastIndex = startIndex + url.length();
                text.setSpan(new CustomTabsURLSpan(url), startIndex, lastIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            return text;
        }
        return source;
    }

    @Override
    public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction, Rect previouslyFocusedRect) {

    }
}

And here is quick explanation: https://medium.com/@nullthemall/make-textview-open-links-in-customtabs-12fdcf4bb684#.ig1chpbbe

Upvotes: 8

Related Questions