国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 系統(tǒng) > Android > 正文

Android使用內(nèi)置WebView打開(kāi)TextView超鏈接的實(shí)現(xiàn)方法

2019-12-12 03:28:36
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

需求原因

最近工作中遇到一個(gè)需求,后來(lái)通過(guò)查找相關(guān)的資料終于解決了,索性記錄下來(lái)分享給大家,需要的朋友們可以參考學(xué)習(xí)。

該需求如下:

**產(chǎn)品說(shuō),我們要實(shí)現(xiàn)問(wèn)答功能,答案內(nèi)的鏈接要使用內(nèi)置的瀏覽器打開(kāi)。

**視覺(jué)說(shuō),我們要給超鏈接標(biāo)上我們自己的顏色。

如圖:

下面我們分析下如何實(shí)現(xiàn)。

使用Html

常規(guī)方法,給定一段標(biāo)準(zhǔn)html文檔,使用Html.fromHtml()封裝,直接使用TextView顯示。

TextView textView = (TextView) findViewById(R.id.detailed_question_tv_answer);String testString ="親,一般遇到這問(wèn)題您可以這樣哦:<br>1.可以<font color='#ff8785'><a ;textView.setMovementMethod(LinkMovementMethod.getInstance());// 設(shè)置鏈接顏色textView.setLinkTextColor(getResources().getColor(R.color.red_ff8785));Spanned htmlString = Html.fromHtml(testString);textView.setText(htmlString);

使用常規(guī)方法無(wú)論怎么設(shè)置,鏈接都會(huì)使用隱式Intent打開(kāi),即使用外部的瀏覽器打開(kāi),不符合咱們產(chǎn)品的需求呀。怎么才能監(jiān)聽(tīng)這個(gè)使用并使用內(nèi)部WebView打開(kāi)呢?使用SpannableStringBuilder。

使用SpannableStringBuilder

直接上代碼。

TextView textView = (TextView) findViewById(R.id.detailed_question_tv_answer);String testString = "親,一般遇到這問(wèn)題您可以這樣哦:<br>1.可以<font color='#ff8785'><a ;textView.setMovementMethod(LinkMovementMethod.getInstance());textView.setLinkTextColor(getResources().getColor(R.color.red_ff8785));String linkText = "催發(fā)貨";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);

當(dāng)然,這個(gè)方法是有很大的局限性的,必須知道鏈接在文案中的具體位置,以及鏈接的地址才能夠使用這種方法。按照這種思路,我們必須使用正則表達(dá)式獲取對(duì)應(yīng)的a標(biāo)簽才能得到鏈接。這種方法拿到的鏈接在文案中的具體位置是難以把握的,很有可能出錯(cuò)。

Html + SpannableStringBuilder

有沒(méi)有第三種方法,即能夠解析到給定文案中的所有Html標(biāo)簽,又能夠使用內(nèi)置的WebView打開(kāi)這個(gè)鏈接?從第一種方法中,我們直接使用Html.fromHtml()方法拿到對(duì)應(yīng)的Spanned結(jié)果,我們可以從這里入手,看看這個(gè)方法是怎么解析html標(biāo)簽的

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轉(zhuǎn)換成Spanned HtmlToSpannedConverter converter =   new HtmlToSpannedConverter(source, imageGetter, tagHandler,     parser); return converter.convert();}

接下來(lái)看一下HtmlToSpannedConverter.convert()這個(gè)方法。HtmlToSpannedConverter實(shí)現(xiàn)了ContentHandler接口,ContentHandler用于處理Xml文檔的解析細(xì)節(jié)。

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;}

我們關(guān)心Html是如何被轉(zhuǎn)換成Spanned就夠了。在整個(gè)解析Html的過(guò)程中,是通過(guò)SpannableStringBuilder.setSpan(Object what, int start, int end, int flags)這個(gè)方法不斷進(jìn)行Html->Spanned轉(zhuǎn)換的。例如,遇到一個(gè)a標(biāo)簽,則會(huì)通過(guò)下邊的方法設(shè)置一個(gè)Span,在SpannabbleStringBuilder內(nèi)部,Span用一個(gè)數(shù)組表示,是可以累加的。

// 遇到a標(biāo)簽頭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標(biāo)簽結(jié)束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標(biāo)簽的轉(zhuǎn)換方法,實(shí)際上,a標(biāo)簽最后被轉(zhuǎn)換成了一個(gè)URLSpan。

看到這里,思路就來(lái)了!實(shí)際上,Html.fromHtml()方法最后轉(zhuǎn)換成的對(duì)象是一個(gè)SpannableStringBuilder,我們可以拿到這個(gè)對(duì)象的引用,然后獲取所有的URLSpan,最后把這些URLSpan全部轉(zhuǎn)換成可以監(jiān)聽(tīng)的事件不就實(shí)現(xiàn)了嗎?實(shí)際上并沒(méi)有這么簡(jiǎn)單,URLSpan是一個(gè)類,繼承自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); }}

這里已經(jīng)默認(rèn)使用了隱式Intent的方式打開(kāi)Uri。我們不能直接改變URLSpan類的實(shí)現(xiàn)方式,但可以繼承這個(gè)類并覆蓋掉它的onClick(View)方法,或者直接繼承ClickableSpan。但是,這樣還是會(huì)有問(wèn)題,原先的URLSpan早就在解析的時(shí)候存在于SpannableStringBuilder中的,我們需要先移除對(duì)應(yīng)的URLSpan,然后再設(shè)置自己實(shí)現(xiàn)的新的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標(biāo)簽相關(guān)的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) {      //先移除這個(gè)Span,再新添加一個(gè)自己實(shí)現(xiàn)的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);}

總結(jié)

TextView真的是Android里最強(qiáng)大的組件之一,復(fù)雜度很高,源碼甚至比Activity還要多。正確的使用TextView具有意想不到的效果~文中為了解決TextView組件中的超鏈接使用App自帶的WebView打開(kāi)這個(gè)問(wèn)題進(jìn)行了一次探討,最終通過(guò)hook拿到URLSpan,移除它并實(shí)現(xiàn)自己的ClickableSpan,最終解決問(wèn)題。好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)各位Android開(kāi)發(fā)們能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 东阿县| 长治市| 大安市| 额尔古纳市| 陈巴尔虎旗| 抚州市| 广丰县| 四子王旗| 蒙阴县| 仁化县| 韶关市| 湖南省| 庄浪县| 苍南县| 华安县| 荥经县| 彰武县| 防城港市| 洱源县| 乌审旗| 封开县| 胶州市| 靖边县| 高清| 革吉县| 滨海县| 绩溪县| 景东| 革吉县| 天长市| 洛扎县| 四子王旗| 大足县| 阜阳市| 南川市| 治县。| 白银市| 昌江| 沅江市| 舞钢市| 定远县|