热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

Android使用内置WebView打开TextView超链接的实现方法

这篇文章主要介绍了Android使用内置WebView打开TextView超链接的实现方法,文中给出了详细的示例代码,对各位Android开发者们具有一定的参考价值,需要的朋友们下面来一起看看吧。

需求原因

最近工作中遇到一个需求,后来通过查找相关的资料终于解决了,索性记录下来分享给大家,需要的朋友们可以参考学习。

该需求如下:

**产品说,我们要实现问答功能,答案内的链接要使用内置的浏览器打开。

**视觉说,我们要给超链接标上我们自己的颜色。

如图:

下面我们分析下如何实现。

使用Html

常规方法,给定一段标准html文档,使用Html.fromHtml()封装,直接使用TextView显示。

TextView textView = (TextView) findViewById(R.id.detailed_question_tv_answer);
String testString =
"亲,一般遇到这问题您可以这样哦:
1.可以催发货哦~
2.然后耐心等待哦~
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 =
 "亲,一般遇到这问题您可以这样哦:
1.可以催发货哦~
2.然后耐心等待哦~
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 = 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开发们能带来一定的帮助,如果有疑问大家可以留言交流。


推荐阅读
  • 资源:吊炸天!74款APP完整源码!android界面中点击输入框时弹出输入法如果输入框在底部会出现输入法遮挡输入内容的问题解决办法设置activity的windowsoftinpu ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • 安卓端开源移动浏览器开源项目
    进入android开发以来,webview用的比较多,后来又看了一些基于webview的浏览器代码,慢慢的将积累的东西做成了一个安卓移动浏 ... [详细]
  • 01mui框架使用概述
    1MUI概述1.1MUI诞生背景?性能和体验的差距,一直是手机APP开发者放弃HTML5的首要原因。浏览器默认控件样式又少又丑,制作一 ... [详细]
  • Android Studio中的IBM MobileFirst Compile问题 - IBM MobileFirst Compile in Android Studio Issue
    IbuiltaMultipageapplicationbyusingIBMMobileFirst,accordingto据我所知,我使用IBMMobileFirst构建了一个 ... [详细]
  • 在使用react-native-scrollable-tab-view这个组件。每一个tab嵌套了一个webview地图。安卓debug下每一个tab都可以都正常显示,打包apk之后有一点问题进 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
  • 安卓开发入门!BAT大厂面试基础题集合,顺利通过阿里Android岗面试
    其实不是Android不行了,而是你跟不上了我的很多读者都在反馈说,现在一个岗位可以收到的简历数,是前几年的几倍。我们必须承认ÿ ... [详细]
  • 开发笔记:Appium自动化测试之微信h5元素识别和代码实战
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Appium自动化测试之微信h5元素识别和代码实战相关的知识,希望对你有一定的参考价值。总会有人问微信的自动化测试怎么做 ... [详细]
  • UIWebView拦截URL原理:js通过加载url方式被webView拦截,这时候看如果是自己定义的scheme请求就不让webView继续加载请求,否则就继续加载请求。webV ... [详细]
author-avatar
suibian
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有