Skip to content
This repository has been archived by the owner on Sep 7, 2020. It is now read-only.

How to I handle the <a> tag #87

Closed
prakashraman opened this issue Feb 13, 2017 · 16 comments
Closed

How to I handle the <a> tag #87

prakashraman opened this issue Feb 13, 2017 · 16 comments
Labels

Comments

@prakashraman
Copy link

I would like the tag to be handled a little differently. I would need a custom handler for this tag. It should technically open one of my WebViews and load the URL in it.

How do I go about doing this?

@tpb1908
Copy link

tpb1908 commented Feb 14, 2017

Do you mean that you want a custom onClick handler for the links?

@prakashraman
Copy link
Author

Yes. That I what I'd like to have.

@tpb1908
Copy link

tpb1908 commented Feb 14, 2017

OK.

If you look at the source for the HtmlTextView class you can see

public void setHtml(@NonNull String html, @Nullable Html.ImageGetter imageGetter) 

Within that method is the following code

if (removeFromHtmlSpace) {
    setText(removeHtmlBottomPadding(Html.fromHtml(html, imageGetter, htmlTagHandler)));
} else {
    setText(Html.fromHtml(html, imageGetter, htmlTagHandler));
}

Html.fromHtml returns an instance of Spanned, containing all the spans that make up your text.

If you have an tag in there, it will have been parsed to URLSpan.

Rather than setting the text straight away, you can modify that part of the method to this

final Spanned text;
if(removeFromHtmlSpace) {
    text = removeHtmlBottomPadding(Html.fromHtml(overridden, imageGetter, htmlTagHandler));
} else {
    text = Html.fromHtml(overridden, imageGetter, htmlTagHandler);
}
               

text now holds the spans that you need.

Now, we want to modify the URLSpans so that they do what you want.

First, we need a click listener.
I assume that you just want to be passed the URL for that span, so we define an interface

public interface LinkClickHandler {

    void onClick(String url);

}

Then we add an instance of this interface and a setter to HtmlTextView.

Secondly, we need to use this listener.
Unfortunately, we can't set the click listener on the pre-existing URLSpan, so we have to replace it.

In order to modify the Spanned, it needs to be a buffer, so we create one.

final SpannableString buffer = new SpannableString(text);

If you want to handle links like www.somewebsite.com as well, you need to enable those separately with Linkify

Linkify.addLinks(buffer, Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);

Now that we get all the URLSpans which we want to replace

final URLSpan[] spans = text.getSpans(0, text.length(), URLSpan.class);

Then we replace them

for(URLSpan us : spans) {
    final int end = text.getSpanEnd(us); //Getting the spans position
    final int start = text.getSpanStart(us);
    buffer.setSpan(new URLSpan(us.getURL()) { // A new span with the same URL
                        @Override
                        public void onClick(View widget) {
                            if(mLinkHandler != null) {
                                mLinkHandler.onClick(getURL());
                            } else {
                               super.OnClick(widget);
                            }
                        }
                    }, start, end, 0); //Overwriting the old span
}

Finally, we set the text

setText(buffer);
setMovementMethod(LocalLinkMovementMethod.getInstance());

In order to use your onClickListener, you use the setter method that you created earlier on your instance of HtmlTextView.

@tpb1908
Copy link

tpb1908 commented Feb 14, 2017

I should probably say, the reason for getting the start and end positions from text rather than the buffer that was just created, is that if you use Linkify, it removes all of the URLSpans that were in the text Spanned.

@vishnumad
Copy link

@tpb1908 Can you clarify on what the super.OnClick(getUrl()); is doing? Shouldn't it be super.onClick(widget)?

@tpb1908
Copy link

tpb1908 commented Mar 3, 2017

@vishnumad Yes it should be.

Either call super.onClick(widget), or you can use the widget context to launch an activity of your choice.

final Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(getURL());
widget.getContext().startActivity(i);

@rubenwardy
Copy link

I'd consider this more of a feature request, it would be nice to have 'HtmlTextView.setOnLinkClick' to override the default opening of this

@tpb1908
Copy link

tpb1908 commented Aug 31, 2017

I ended up modifying the whole thing a while back
https://github.com/tpb1908/AndroidProjectsClient/tree/master/markdowntextview/src/main/java/com/tpb/mdtext

to allow click handling for code, images, links, and tables.

There is also an EditText version which can be toggled between raw markdown and formatted, as well as a WebView version and support for Emoji tags.

Also inline code spans and proper strikethroughs, tick box lists, relative image links for github, as well as github issue and user links.

@rubenwardy
Copy link

rubenwardy commented Aug 31, 2017

That looks pretty useful! Bookmarked.


The links still open in Chrome for me. I followed your steps basically, and also tried disabling linksClickable

Edit: Fixed, I needed to set text to buffer not the old span. Oops.

Here is my code for reference: https://gist.github.com/rubenwardy/a503aaaee25f02566dc98654809d55cb#file-myhtmltextview-kt-L52

@tpb1908
Copy link

tpb1908 commented Aug 31, 2017

It's a bit of a mess hacked together for a college project. I have been meaning to rewrite the whole thing in Kotlin with some proper architecture.

@petitJAM
Copy link

petitJAM commented Sep 7, 2018

@rubenwardy You should try out https://github.com/saket/Better-Link-Movement-Method

The trick is that you need to call linkifyHtml after you call setHtml. The setHtml method internally calls setMovementMethod, which will mess up BetterLinkMovementMethod.

I extracted the OnLinkClickListener to a field so that a bunch of objects weren't being created every time it scrolled.

class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    private val onLinkClickLinkClickListener =
            BetterLinkMovementMethod.OnLinkClickListener { textView, url ->
                // Handle URL
                true
            }

    fun bind(html: String) {
        itemView.htmlTextView.setHtml(html)
        BetterLinkMovementMethod
                .linkifyHtml(itemView.htmlTextView)
                .setOnLinkClickListener(onLinkClickLinkClickListener)
    }
}

@rubenwardy
Copy link

rubenwardy commented Sep 7, 2018

I ended up just using a web view, and have since resigned from the company so :D

@petitJAM
Copy link

petitJAM commented Sep 7, 2018

Welp, I guess that works too :D

@xiandanin
Copy link
Contributor

You can look here.
dengyuhan/html-textview

@fikr4n
Copy link

fikr4n commented Oct 8, 2019

You can look here.
dengyuhan/html-textview

Your fork uses other library instead of what the original one uses. Besides that, the leading margin of the bullet/number is too small and cannot be customized.

@xiandanin
Copy link
Contributor

xiandanin commented Dec 19, 2019

@fikr4n You are right, so I modified it based on the original library, now it can support the click event of a tag.

see xiandanin/html-textview or #181

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

8 participants