欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2017年03月06日 12:00:00   作者:Xing  
這篇文章主要介紹了Android使用內(nèi)置WebView打開TextView超鏈接的實(shí)現(xiàn)方法,文中給出了詳細(xì)的示例代碼,對(duì)各位Android開發(fā)者們具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。

需求原因

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

該需求如下:

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

**視覺說,我們要給超鏈接標(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 =
"親,一般遇到這問題您可以這樣哦:<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ī)方法無論怎么設(shè)置,鏈接都會(huì)使用隱式Intent打開,即使用外部的瀏覽器打開,不符合咱們產(chǎn)品的需求呀。怎么才能監(jiān)聽這個(gè)使用并使用內(nèi)部WebView打開呢?使用SpannableStringBuilder。

使用SpannableStringBuilder

直接上代碼。

TextView textView = (TextView) findViewById(R.id.detailed_question_tv_answer);
String testString =
 "親,一般遇到這問題您可以這樣哦:<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

有沒有第三種方法,即能夠解析到給定文案中的所有Html標(biāo)簽,又能夠使用內(nèi)置的WebView打開這個(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();
}

接下來看一下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的過程中,是通過SpannableStringBuilder.setSpan(Object what, int start, int end, int flags)這個(gè)方法不斷進(jìn)行Html->Spanned轉(zhuǎn)換的。例如,遇到一個(gè)a標(biāo)簽,則會(huì)通過下邊的方法設(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。

看到這里,思路就來了!實(shí)際上,Html.fromHtml()方法最后轉(zhuǎn)換成的對(duì)象是一個(gè)SpannableStringBuilder,我們可以拿到這個(gè)對(duì)象的引用,然后獲取所有的URLSpan,最后把這些URLSpan全部轉(zhuǎn)換成可以監(jiān)聽的事件不就實(shí)現(xiàn)了嗎?實(shí)際上并沒有這么簡(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的方式打開Uri。我們不能直接改變URLSpan類的實(shí)現(xiàn)方式,但可以繼承這個(gè)類并覆蓋掉它的onClick(View)方法,或者直接繼承ClickableSpan。但是,這樣還是會(huì)有問題,原先的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打開這個(gè)問題進(jìn)行了一次探討,最終通過hook拿到URLSpan,移除它并實(shí)現(xiàn)自己的ClickableSpan,最終解決問題。好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)各位Android開發(fā)們能帶來一定的幫助,如果有疑問大家可以留言交流。

相關(guān)文章

  • Android架構(gòu)組件Room的使用詳解

    Android架構(gòu)組件Room的使用詳解

    Room其實(shí)就是一個(gè)orm,抽象了SQLite的使用。這篇文章給大家介紹了Android架構(gòu)組件Room的使用詳解,需要的朋友參考下吧
    2017-12-12
  • RxJava2.x實(shí)現(xiàn)定時(shí)器的實(shí)例代碼

    RxJava2.x實(shí)現(xiàn)定時(shí)器的實(shí)例代碼

    本篇文章主要介紹了RxJava2.x實(shí)現(xiàn)定時(shí)器,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • Android實(shí)現(xiàn)左滑退出Activity的完美封裝

    Android實(shí)現(xiàn)左滑退出Activity的完美封裝

    這篇文章主要介紹了Android實(shí)現(xiàn)左滑退出Activity的完美封裝,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • android 仿微信demo——微信主界面實(shí)現(xiàn)

    android 仿微信demo——微信主界面實(shí)現(xiàn)

    本系列文章主要介紹了微信小程序-閱讀小程序?qū)嵗╠emo),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望能給你們提供幫助
    2021-06-06
  • Android中檢查、監(jiān)聽電量和充電狀態(tài)的方法

    Android中檢查、監(jiān)聽電量和充電狀態(tài)的方法

    這篇文章主要介紹了Android中檢查、監(jiān)聽電量和充電狀態(tài)的方法,如判斷當(dāng)前充電狀態(tài)、監(jiān)聽充電狀態(tài)的改變、判斷當(dāng)前剩余電量等,需要的朋友可以參考下
    2014-06-06
  • 詳解Android布局優(yōu)化

    詳解Android布局優(yōu)化

    本篇文章給大家詳細(xì)分析了Android布局優(yōu)化的相關(guān)知識(shí)點(diǎn)以及注意事項(xiàng),對(duì)此有需要的朋友可以參考學(xué)習(xí)下。
    2018-03-03
  • Android編程之下拉菜單Spinner控件用法示例

    Android編程之下拉菜單Spinner控件用法示例

    這篇文章主要介紹了Android編程之下拉菜單Spinner控件用法,結(jié)合簡(jiǎn)單實(shí)例形式分析了Android下拉菜單Spinner的布局與功能相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-07-07
  • android使用AES加密和解密文件實(shí)例代碼

    android使用AES加密和解密文件實(shí)例代碼

    本篇文章主要介紹了android使用AES加密和解密文件實(shí)例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-05-05
  • Android之開發(fā)消息通知欄

    Android之開發(fā)消息通知欄

    本文主要介紹了Android開發(fā)消息通知欄的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來看下吧
    2017-04-04
  • Android仿eleme點(diǎn)餐頁(yè)面二級(jí)聯(lián)動(dòng)列表

    Android仿eleme點(diǎn)餐頁(yè)面二級(jí)聯(lián)動(dòng)列表

    本站一直在點(diǎn)外賣,于是心血來潮就像仿餓了么做個(gè)站,接下來通過本文給大家介紹android 二級(jí)聯(lián)動(dòng)列表,仿eleme點(diǎn)餐頁(yè)面的相關(guān)資料,需要的朋友可以參考下
    2016-10-10

最新評(píng)論