需求
项目中常有需求:要求长段文本中实现点击局部文本并执行自定义事件。本文针对类似需求进行简易的分析并编写相关工具类。
分析
首先多TextView实现肯定是不行的:一是代码冗余;二是若可点击文本换行时不好处理。
接着看其效果类似超链接,自然想到使用Html.fromHtml,将可点击区域加上a标签。可是点击a标签默认会调用外部浏览器打开指定链接。跟进Html.fromHtml方法可知,其返回的是一个Spanned
public static Spanned fromHtml(String source)
复制代码
既然是Spanned,那自然是可以自定义的。
思路:给可点击文本加上a标签,获取Html.fromHtml返回的Spanned中的UrlSpan部分,在相同位置添加自定义ClickSpan实现自定义事件,代码如下:
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();//获取a标签中的url
//添加点击事件
}
复制代码
同时创建一个map,key可以为url,value为相应的点击事件。添加ClickSpan同时可以添加其他样式Span。
完整代码:
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) {
//点击事件
builder.setSpan(new ClickSpan(option.runnable, option.underline),
html.getSpanStart(span),
html.getSpanEnd(span), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
//颜色
if (option.color != 0) {
builder.setSpan(new ForegroundColorSpan(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) {
//下划线
ds.setUnderlineText(mUnderline);
}
}
}
复制代码
使用
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, "事件2", Toast.LENGTH_SHORT).show(), 0x0000FF, false));
textView.setText(ClickSpanUtils.parse("This is the <a href='https://juejin.cn/post/event_one'>interface</a> for text that has markup <a href='https://juejin.cn/post/event_two'>objects</a> attached to ranges of it. ",events));
textView.setMovementMethod(LinkMovementMethod.getInstance());//必须
复制代码
效果
如果使用kotlin,可以使用拓展函数,实现更优雅,这里就不展开了。
如有其它实现方式,欢迎交流!
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END