demand

Projects often have requirements that require long sections of text to be clickable on local text and perform custom events. In this paper, the similar requirements for simple analysis and preparation of the relevant tool class.

Analysis of the

First of all, the implementation of multiple TextView is definitely not possible: one is code redundancy; Second, if the clickable text newline is not easy to handle.

Then it looks like a hyperlink, and the natural idea is to use html.fromhtml and tag the clickable area with an A tag. Clicking the A TAB, however, by default invokes an external browser to open the specified link. Following the html.fromhtml method, it returns a Spanned

public static Spanned fromHtml(String source)
Copy the code

Since it’s Spanned, it’s customizable.

Add a tag to the clickable text to retrieve the UrlSpan portion of the Spanned returned by html. fromHtml. Add a custom ClickSpan event in the same place:

Spanned html = Html.fromHtml(text);
URLSpan[] spans = html.getSpans(0, text.length(), URLSpan.class);
SpannableStringBuilder builder = new SpannableStringBuilder(html);
builder.clearSpans();
for (URLSpan span : spans) {
	  String url = span.getURL();// Get the URL in the a tag
  	// Add click events
}
Copy the code

Create a map at the same time. The key can be the URL and the value can be the corresponding click event. Adding ClickSpan allows you to add other style spans.

Complete code:

public class ClickSpanUtils {
    public static class ClickOption {
        public Runnable runnable;
        public int color;
        public boolean underline;

        public ClickOption(Runnable runnable, int color, boolean underline) {
            this.runnable = runnable;
            this.color = color;
            this.underline = underline;
        }

        public ClickOption(Runnable runnable, int color) {
            this(runnable, color, true);
        }

        public ClickOption(Runnable runnable) {
            this(runnable, 0); }}public static Spanned parse(String text, Map<String, ClickOption> eventMap) {
        Spanned html = Html.fromHtml(text);
        URLSpan[] spans = html.getSpans(0, text.length(), URLSpan.class);
        SpannableStringBuilder builder = new SpannableStringBuilder(html);
        builder.clearSpans();
        for (URLSpan span : spans) {
            String url = span.getURL();
            ClickOption option = eventMap.get(url);
            if(option ! =null) {
                // Click the event
                builder.setSpan(new ClickSpan(option.runnable, option.underline),
                        html.getSpanStart(span),
                        html.getSpanEnd(span), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                / / color
                if(option.color ! =0) {
                    builder.setSpan(newForegroundColorSpan(option.color), html.getSpanStart(span), html.getSpanEnd(span), Spanned.SPAN_INCLUSIVE_INCLUSIVE); }}}return builder;
    }

    public static class ClickSpan extends ClickableSpan {
        Runnable mRunnable;
        boolean mUnderline;

        public ClickSpan(Runnable runnable, boolean underline) {
            mRunnable = runnable;
            mUnderline = underline;
        }

        @Override
        public void onClick(View view) {
            mRunnable.run();
        }

        @Override
        public void updateDrawState(@NonNull TextPaint ds) {
            / / the underlineds.setUnderlineText(mUnderline); }}}Copy the code

use

Map<String, ClickSpanUtils.ClickOption> events = new HashMap<>();
events.put("event_one".new ClickSpanUtils.ClickOption(
  () -> Toast.makeText(this."1", Toast.LENGTH_SHORT).show(), 0xFF0000));
events.put("event_two".new ClickSpanUtils.ClickOption(
  () -> Toast.makeText(this."Event 2", Toast.LENGTH_SHORT).show(), 0x0000FF.false));

textView.setText(ClickSpanUtils.parse("This is the <a href='event_one'>interface</a> for text that has markup <a href='event_two'>objects</a> attached to ranges of it. ",events));
textView.setMovementMethod(LinkMovementMethod.getInstance());/ / must
Copy the code

The effect

If you use Kotlin, you can use extension functions to make it more elegant, so I won’t expand it here.

If there are other ways to achieve, welcome to exchange!