需求原因

最近工作中遇到一个需求,后来通过查找相关的资料终于解决了,索性记录下来分享给大家,需要的朋友们可以参考学习。
该需求如下:
**产品说,我们要实现问答功能,答案内的链接要使用内置的浏览器打开。
**视觉说,我们要给超链接标上我们自己的颜色。
如图:
下面我们分析下如何实现。
使用Html
常规方法,给定一段标准html文档,使用Html.fromHtml()封装,直接使用TextView显示。
TextView textView = (TextView) findViewById(R.id.detailed_question_tv_answer); String testString = "亲,一般遇到这问题您可以这样哦:<br>1.可以<font color='#ff8785'><a href='http://m.kaola.com'>催发货</a></font>哦~<br>2.然后耐心等待哦~<br>3.1-3天后新也可以拨打我们的客服."; textView.setMovementMethod(LinkMovementMethod.getInstance()); // 设置链接颜色 textView.setLinkTextColor(getResources().getColor(R.color.red_ff8785)); Spanned htmlString = Html.fromHtml(testString); textView.setText(htmlString);
使用常规方法无论怎么设置,链接都会使用隐式Intent打开,即使用外部的浏览器打开,不符合咱们产品的需求呀。怎么才能监听这个使用并使用内部WebView打开呢?使用SpannableStringBuilder。
使用SpannableStringBuilder
直接上代码。
TextView textView = (TextView) findViewById(R.id.detailed_question_tv_answer);
String testString =
"亲,一般遇到这问题您可以这样哦:<br>1.可以<font color='#ff8785'><a href='http://m.kaola.com'>催发货</a></font>哦~<br>2.然后耐心等待哦~<br>3.1-3天后新也可以拨打我们的客服.";
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setLinkTextColor(getResources().getColor(R.color.red_ff8785));
String linkText = "催发货";
int startIndexOfLink = testString.indexOf(linkText);
int endIndexOfLink = startIndexOfLink + linkText.length();
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(testString);
spannableStringBuilder.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
ActivityUtils.startWebviewActivity(DetailedQuestionActivity.this, "http://m.kaola.com", false);
}
}, startIndexOfLink, endIndexOfLink, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setText(spannableStringBuilder);
当然,这个方法是有很大的局限性的,必须知道链接在文案中的具体位置,以及链接的地址才能够使用这种方法。按照这种思路,我们必须使用正则表达式获取对应的a标签才能得到链接。这种方法拿到的链接在文案中的具体位置是难以把握的,很有可能出错。
Html + SpannableStringBuilder
有没有第三种方法,即能够解析到给定文案中的所有Html标签,又能够使用内置的WebView打开这个链接?从第一种方法中,我们直接使用Html.fromHtml()方法拿到对应的Spanned结果,我们可以从这里入手,看看这个方法是怎么解析html标签的
public static Spanned fromHtml(String source, ImageGetter imageGetter,
TagHandler tagHandler) {
// 使用org.ccil.cowan.tagsoup.Parser作为解析器
Parser parser = new Parser();
try {
parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
} catch (org.xml.sax.SAXNotRecognizedException e) {
// Should not happen.
throw new RuntimeException(e);
} catch (org.xml.sax.SAXNotSupportedException e) {
// Should not happen.
throw new RuntimeException(e);
}
// 使用HtmlToSpannedConverter将Ttml转换成Spanned
HtmlToSpannedConverter converter =
new HtmlToSpannedConverter(source, imageGetter, tagHandler,
parser);
return converter.convert();
}
接下来看一下HtmlToSpannedConverter.convert()这个方法。HtmlToSpannedConverter实现了ContentHandler接口,ContentHandler用于处理Xml文档的解析细节。
public Spanned convert() {
mReader.setContentHandler(this);
try {
mReader.parse(new InputSource(new StringReader(mSource)));
} catch (IOException e) {
// We are reading from a string. There should not be IO problems.
throw new RuntimeException(e);
} catch (SAXException e) {
// TagSoup doesn't throw parse exceptions.
throw new RuntimeException(e);
}
// Fix flags and range for paragraph-type markup.
Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class);
for (int i = 0; i < obj.length; i++) {
int start = mSpannableStringBuilder.getSpanStart(obj[i]);
int end = mSpannableStringBuilder.getSpanEnd(obj[i]);
// If the last line of the range is blank, back off by one.
if (end - 2 >= 0) {
if (mSpannableStringBuilder.charAt(end - 1) == '\n' &&
mSpannableStringBuilder.charAt(end - 2) == '\n') {
end--;
}
}
if (end == start) {
mSpannableStringBuilder.removeSpan(obj[i]);
} else {
mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH);
}
}
return mSpannableStringBuilder;
}
我们关心Html是如何被转换成Spanned就够了。在整个解析Html的过程中,是通过SpannableStringBuilder.setSpan(Object what, int start, int end, int flags)这个方法不断进行Html->Spanned转换的。例如,遇到一个a标签,则会通过下边的方法设置一个Span,在SpannabbleStringBuilder内部,Span用一个数组表示,是可以累加的。
// 遇到a标签头
private static void startA(SpannableStringBuilder text, Attributes attributes) {
String href = attributes.getValue("", "href");
int len = text.length();
text.setSpan(new Href(href), len, len, Spannable.SPAN_MARK_MARK);
}
// a标签结束
private static void endA(SpannableStringBuilder text) {
int len = text.length();
Object obj = getLast(text, Href.class);
int where = text.getSpanStart(obj);
text.removeSpan(obj);
if (where != len) {
Href h = (Href) obj;
if (h.mHref != null) {
text.setSpan(new URLSpan(h.mHref), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
可以看到a标签的转换方法,实际上,a标签最后被转换成了一个URLSpan。
看到这里,思路就来了!实际上,Html.fromHtml()方法最后转换成的对象是一个SpannableStringBuilder,我们可以拿到这个对象的引用,然后获取所有的URLSpan,最后把这些URLSpan全部转换成可以监听的事件不就实现了吗?实际上并没有这么简单,URLSpan是一个类,继承自ClickableSpan,覆盖了其中的onClick(View)方法:
public class URLSpan extends ClickableSpan implements ParcelableSpan {
private final String mURL;
public URLSpan(String url) {
mURL = url;
}
public URLSpan(Parcel src) {
mURL = src.readString();
}
public int getSpanTypeId() {
return TextUtils.URL_SPAN;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mURL);
}
public String getURL() {
return mURL;
}
@Override
public void onClick(View widget) {
Uri uri = Uri.parse(getURL());
Context context = widget.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
context.startActivity(intent);
}
}
这里已经默认使用了隐式Intent的方式打开Uri。我们不能直接改变URLSpan类的实现方式,但可以继承这个类并覆盖掉它的onClick(View)方法,或者直接继承ClickableSpan。但是,这样还是会有问题,原先的URLSpan早就在解析的时候存在于SpannableStringBuilder中的,我们需要先移除对应的URLSpan,然后再设置自己实现的新的ClickableSpan就可以了。
具体代码如下:
public static SpannableStringBuilder setTextLinkOpenByWebView(final Context context, String answerString) {
if (!TextUtils.isEmpty(answerString)) {
Spanned htmlString = Html.fromHtml(answerString);
if (htmlString instanceof SpannableStringBuilder) {
SpannableStringBuilder spannableStringBuilder = (SpannableStringBuilder) htmlString;
// 取得与a标签相关的Span
Object[] objs = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), URLSpan.class);
if (null != objs && objs.length != 0) {
for (Object obj : objs) {
int start = spannableStringBuilder.getSpanStart(obj);
int end = spannableStringBuilder.getSpanEnd(obj);
if (obj instanceof URLSpan) {
//先移除这个Span,再新添加一个自己实现的Span。
URLSpan span = (URLSpan) obj;
final String url = span.getURL();
spannableStringBuilder.removeSpan(obj);
spannableStringBuilder.setSpan(new ClickableSpan() {
@Override
public void onClick(View widget) {
ActivityUtils.startWebviewActivity(context, url, true);
}
}, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
return spannableStringBuilder;
}
}
return new SpannableStringBuilder(answerString);
}
总结
TextView真的是Android里最强大的组件之一,复杂度很高,源码甚至比Activity还要多。正确的使用TextView具有意想不到的效果~文中为了解决TextView组件中的超链接使用App自带的WebView打开这个问题进行了一次探讨,最终通过hook拿到URLSpan,移除它并实现自己的ClickableSpan,最终解决问题。好了,以上就是这篇文章的全部内容了,希望本文的内容对各位Android开发们能带来一定的帮助,如果有疑问大家可以留言交流。
# webview
# 超链接
# 点击超链接
# textview
# Android 如何使用短信链接打开APP
# Android编程实现点击链接打开APP功能示例
# Android应用中实现跳转外部浏览器打开链接功能
# 转换成
# 自己的
# 是一个
# 移除
# 客服
# 您可以
# 我们可以
# 标上
# 种方法
# 耐心等待
# 的是
# 实现了
# 文档
# 来了
# 会有
# 成了
# 好了
# 这种方法
# 是有
相关文章:
上海网站制作网页,上海本地的生活网站有哪些?最好包括生活的各个方面的?
活动邀请函制作网站有哪些,活动邀请函文案?
如何基于云服务器快速搭建网站及云盘系统?
建站之星如何助力网站排名飙升?揭秘高效技巧
如何快速搭建响应式可视化网站?
香港服务器网站搭建教程-电商部署、配置优化与安全稳定指南
建站之星如何快速更换网站模板?
网页设计与网站制作内容,怎样注册网站?
湖北网站制作公司有哪些,湖北清能集团官网?
,石家庄四十八中学官网?
如何零成本快速生成个人自助网站?
制作网站公司那家好,网络公司是做什么的?
文字头像制作网站推荐软件,醒图能自动配文字吗?
制作网站建设的公司有哪些,网站建设比较好的公司都有哪些?
装修招标网站设计制作流程,装修招标流程?
平台云上自助建站如何快速打造专业网站?
制作网站的网址是什么,请问后缀为.com和.com.cn还有.cn的这三种网站是分别是什么类型的网站?
建站之星后台密码遗忘如何找回?
网站设计制作书签怎么做,怎样将网页添加到书签/主页书签/桌面?
完全自定义免费建站平台:主题模板在线生成一站式服务
建站主机与虚拟主机有何区别?如何选择最优方案?
娃派WAP自助建站:免费模板+移动优化,快速打造专业网站
太平洋网站制作公司,网络用语太平洋是什么意思?
如何在万网开始建站?分步指南解析
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
深圳网站制作费用多少钱,读秀,深圳文献港这样的网站很多只提供网上试读,但有些人只要提供试读的文章就能全篇下载,这个是怎么弄的?
如何用腾讯建站主机快速创建免费网站?
建站之星好吗?新手能否轻松上手建站?
建站主机选购指南:核心配置优化与品牌推荐方案
网站设计制作公司地址,网站建设比较好的公司都有哪些?
香港服务器部署网站为何提示未备案?
网站专业制作公司有哪些,做一个公司网站要多少钱?
常州企业建站如何选择最佳模板?
建站之星后台搭建步骤解析:模板选择与产品管理实操指南
制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?
如何在橙子建站中快速调整背景颜色?
如何快速登录WAP自助建站平台?
如何快速生成橙子建站落地页链接?
网站海报制作教学视频教程,有什么免费的高清可商用图片网站,用于海报设计?
韩国代理服务器如何选?解析IP设置技巧与跨境访问优化指南
测试制作网站有哪些,测试性取向的权威测试或者网站?
,有什么在线背英语单词效率比较高的网站?
建设网站制作价格,怎样建立自己的公司网站?
Python文件管理规范_工程实践说明【指导】
韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐
香港服务器建站指南:免备案优势与SEO优化技巧全解析
阿里云网站搭建费用解析:服务器价格与建站成本优化指南
如何在企业微信快速生成手机电脑官网?
建站主机如何选?高性价比方案全解析
武清网站制作公司,天津武清个人营业执照注销查询系统网站?
*请认真填写需求信息,我们会在24小时内与您取得联系。