最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android利用SpannableString實(shí)現(xiàn)格式化微博內(nèi)容

 更新時(shí)間:2017年03月09日 11:07:48   作者:Melodyxxx  
這篇文章主要介紹了Android利用SpannableString實(shí)現(xiàn)格式化微博內(nèi)容的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考借鑒價(jià)值,需要的朋友們下面來(lái)一起看看吧。

前言

在Android開(kāi)發(fā)中,有許多信息展示需要通過(guò)TextView來(lái)展現(xiàn),如果只是普通的信息展現(xiàn),使用TextView setText(CharSequence str)設(shè)置即可,但是當(dāng)在TextView里的這段內(nèi)容需要截取某一部分字段,可以被點(diǎn)擊以及響應(yīng)響應(yīng)的操作,這時(shí)候就需要用到SpannableString了,SpannableString 配合 TextView 可以輕松實(shí)現(xiàn)對(duì)特定的文本做特定處理,例如可以修改文字顏色、背景色、將文字替換為圖片實(shí)現(xiàn),點(diǎn)擊效果等。

首先看看最終實(shí)現(xiàn)的效果圖:

第一個(gè)卡片內(nèi)的微博是原始文本信息,第二個(gè)卡片內(nèi)的微博是第一個(gè)格式化后的文本內(nèi)容,將微博內(nèi)的”話(huà)題”、”表情”、”網(wǎng)頁(yè)鏈接”、以及”@用戶(hù)”都進(jìn)行了處理,并可以點(diǎn)擊,使其和官方微博展示的樣式保持一致。

要實(shí)現(xiàn)的效果:

  1. 將話(huà)題進(jìn)行變色并且可以點(diǎn)擊提示對(duì)應(yīng)的話(huà)題文本內(nèi)容
  2. 將圖片表情替換掉對(duì)應(yīng)的表情關(guān)鍵字顯示
  3. 將鏈接地址替換成一個(gè)鏈接的圖片和”網(wǎng)頁(yè)鏈接”四個(gè)字顯示
  4. 將@的用戶(hù)進(jìn)行變色并且可以點(diǎn)擊提示對(duì)應(yīng)的話(huà)題文本內(nèi)容

需要:

  1. 使用正則表達(dá)式提取文本內(nèi)對(duì)應(yīng)的”話(huà)題”、”表情”、”網(wǎng)頁(yè)鏈接”、以及”@用戶(hù)”內(nèi)容
  2. 使用 SpannableString 格式化提取到的文本
  3. 給格式化的部分添加點(diǎn)擊事件

定義正則表達(dá)式

首先定義”話(huà)題”、”表情”、”網(wǎng)頁(yè)鏈接”、以及”@用戶(hù)”對(duì)應(yīng)的正則表達(dá)式和對(duì)應(yīng)的 Pattern。SCHEME 下文會(huì)提到具體的用處的。

public class WeiboPattern {

 // #話(huà)題#
 public static final String REGEX_TOPIC = "#[\\p{Print}\\p{InCJKUnifiedIdeographs}&&[^#]]+#";
 // [表情]
 public static final String REGEX_EMOTION = "\\[(\\S+?)\\]";
 // url
 public static final String REGEX_URL = "http://[a-zA-Z0-9+&@#/%?=~_\\\\-|!:,\\\\.;]*[a-zA-Z0-9+&@#/%=~_|]";
 // @人
 public static final String REGEX_AT = "@[\\w\\p{InCJKUnifiedIdeographs}-]{1,26}";

 
 public static final Pattern PATTERN_TOPIC = Pattern.compile(REGEX_TOPIC);
 public static final Pattern PATTERN_EMOTION = Pattern.compile(REGEX_EMOTION);
 public static final Pattern PATTERN_URL = Pattern.compile(REGEX_URL);
 public static final Pattern PATTERN_AT = Pattern.compile(REGEX_AT);

 public static final String SCHEME_TOPIC = "topic:";
 public static final String SCHEME_URL = "url:";
 public static final String SCHEME_AT = "at:";

}

提取匹配部分并使用 SpannableString 格式化

我將此過(guò)程寫(xiě)到一個(gè)方法內(nèi)了,下面直接上代碼,代碼中有詳細(xì)的注釋解釋?zhuān)?/p>

/**
 * 格式化微博文本
 *
 * @param context 上下文
 * @param source 源文本
 * @param textView 目標(biāo) TextView
 * @return SpannableStringBuilder
 */
public static SpannableStringBuilder formatWeiBoContent(Context context, String source, TextView textView) {

 // 獲取到 TextView 的文字大小,后面的 ImageSpan 需要用到該值
 int textSize = (int) textView.getTextSize();

 // 若要部分 SpannableString 可點(diǎn)擊,需要如下設(shè)置
 textView.setMovementMethod(LinkMovementMethod.getInstance());

 // 將要格式化的 String 構(gòu)建成一個(gè) SpannableStringBuilder
 SpannableStringBuilder value = new SpannableStringBuilder(source);

 // 使用正則匹配話(huà)題
 Linkify.addLinks(value, WeiboPattern.PATTERN_TOPIC, WeiboPattern.SCHEME_TOPIC);
 // 使用正則匹配鏈接
 Linkify.addLinks(value, WeiboPattern.PATTERN_URL, WeiboPattern.SCHEME_URL);
 // 使用正則匹配@用戶(hù)
 Linkify.addLinks(value, WeiboPattern.PATTERN_AT, WeiboPattern.SCHEME_AT);

 // 自定義的匹配部分的點(diǎn)擊效果
 MyClickableSpan clickSpan;

 // 獲取上面到所有 addLinks 后的匹配部分(這里一個(gè)匹配項(xiàng)被封裝成了一個(gè) URLSpan 對(duì)象)
 URLSpan[] urlSpans = value.getSpans(0, value.length(), URLSpan.class);

 // 遍歷所有的 URLSpan
 for (final URLSpan urlSpan : urlSpans) {
 // 點(diǎn)擊匹配部分效果
  clickSpan = new MyClickableSpan() {
   @Override
   public void onClick(View view) {
    ToastUtils.makeShort(urlSpan.getURL());
   }
  };
  // 話(huà)題
  if (urlSpan.getURL().startsWith(WeiboPattern.SCHEME_TOPIC)) {
   int start = value.getSpanStart(urlSpan);
   int end = value.getSpanEnd(urlSpan);
   value.removeSpan(urlSpan);
   // 格式化話(huà)題部分文本
   value.setSpan(clickSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  }
  // @用戶(hù)
  if (urlSpan.getURL().startsWith(WeiboPattern.SCHEME_AT)) {
   int start = value.getSpanStart(urlSpan);
   int end = value.getSpanEnd(urlSpan);
   value.removeSpan(urlSpan);
   // 格式化@用戶(hù)部分文本
   value.setSpan(clickSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  }
  // 鏈接
  if (urlSpan.getURL().startsWith(WeiboPattern.SCHEME_URL)) {
   int start = value.getSpanStart(urlSpan);
   int end = value.getSpanEnd(urlSpan);
   value.removeSpan(urlSpan);
   SpannableStringBuilder urlSpannableString = getUrlTextSpannableString(context, urlSpan.getURL(), textSize);
   value.replace(start, end, urlSpannableString);
   // 格式化鏈接部分文本
   value.setSpan(clickSpan, start, start + urlSpannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  }
 }

 // 表情需要單獨(dú)格式化
 Matcher emotionMatcher = WeiboPattern.PATTERN_EMOTION.matcher(value);
 while (emotionMatcher.find()) {
  String emotion = emotionMatcher.group();
  int start = emotionMatcher.start();
  int end = emotionMatcher.end();
  int resId = EmotionUtils.getImageByName(emotion);
  if (resId != -1) { // 表情匹配
   L.e("find emotion: " + emotion);
   Drawable drawable = context.getResources().getDrawable(resId);
   drawable.setBounds(0, 0, (int) (textSize * 1.3), (int) (textSize * 1.3));
   // 自定義的 VerticalImageSpan ,可解決默認(rèn)的 ImageSpan 不垂直居中的問(wèn)題
   VerticalImageSpan imageSpan = new VerticalImageSpan(drawable);
   value.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  }
 }

 return value;
}
private static SpannableStringBuilder getUrlTextSpannableString(Context context, String source, int size) {
 SpannableStringBuilder builder = new SpannableStringBuilder(source);
 String prefix = " ";
 builder.replace(0, prefix.length(), prefix);
 Drawable drawable = context.getResources().getDrawable(R.drawable.ic_status_link);
 drawable.setBounds(0, 0, size, size);
 builder.setSpan(new VerticalImageSpan(drawable), prefix.length(), source.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 builder.append(" 網(wǎng)頁(yè)鏈接");
 return builder;
}

getUrlTextSpannableString() :方法是用來(lái)返回一個(gè)圖標(biāo)+”網(wǎng)頁(yè)鏈接” SpannableString,用于替換鏈接文本

上面將”話(huà)題”、”表情”、”網(wǎng)頁(yè)鏈接”都用了addLinks方法來(lái)標(biāo)記的,然后統(tǒng)一處理。表情則是單獨(dú)處理的。

表情則使用如下方法事先做好映射:

public class EmotionUtils {

 public static LinkedHashMap<String, Integer> sMap;

 static {
  sMap = new LinkedHashMap<>();
  sMap.put("[doge]", R.drawable.d_doge);
  sMap.put("[污]", R.drawable.d_wu);
 }

 public static int getImageByName(String name) {
  Integer integer = sMap.get(name);
  return integer == null ? -1 : integer;
 }

}

還有剛才說(shuō)到的自定義 MyClickableSpan 修改默認(rèn)的樣式:

public class MyClickableSpan extends ClickableSpan {

 @Override
 public void onClick(View view) {

 }

 @Override
 public void updateDrawState(TextPaint ds) {
  super.updateDrawState(ds);
  ds.setColor(0xff03A9F4);
  ds.setUnderlineText(false);
 }

}

另外,由于默認(rèn)的 ImageSpan 在 TextView 有使用android:lineSpacingExtra屬性時(shí),不會(huì)垂直居中,所以使用到了網(wǎng)上的一個(gè)繼承自 ImageSpan 的 VerticalImageSpan 可以做到保持圖片在 TextView 內(nèi)保持垂直居中:

public class VerticalImageSpan extends ImageSpan {

 public VerticalImageSpan(Drawable drawable) {
  super(drawable);
 }

 /**
  * update the text line height
  */
 @Override
 public int getSize(Paint paint, CharSequence text, int start, int end,
      Paint.FontMetricsInt fontMetricsInt) {
  Drawable drawable = getDrawable();
  Rect rect = drawable.getBounds();
  if (fontMetricsInt != null) {
   Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
   int fontHeight = fmPaint.descent - fmPaint.ascent;
   int drHeight = rect.bottom - rect.top;
   int centerY = fmPaint.ascent + fontHeight / 2;

   fontMetricsInt.ascent = centerY - drHeight / 2;
   fontMetricsInt.top = fontMetricsInt.ascent;
   fontMetricsInt.bottom = centerY + drHeight / 2;
   fontMetricsInt.descent = fontMetricsInt.bottom;
  }
  return rect.right;
 }

 /**
  * see detail message in android.text.TextLine
  *
  * @param canvas the canvas, can be null if not rendering
  * @param text the text to be draw
  * @param start the text start position
  * @param end the text end position
  * @param x  the edge of the replacement closest to the leading margin
  * @param top the top of the line
  * @param y  the baseline
  * @param bottom the bottom of the line
  * @param paint the work paint
  */
 @Override
 public void draw(Canvas canvas, CharSequence text, int start, int end,
      float x, int top, int y, int bottom, Paint paint) {

  Drawable drawable = getDrawable();
  canvas.save();
  Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
  int fontHeight = fmPaint.descent - fmPaint.ascent;
  int centerY = y + fmPaint.descent - fontHeight / 2;
  int transY = centerY - (drawable.getBounds().bottom - drawable.getBounds().top) / 2;
  canvas.translate(x, transY);
  drawable.draw(canvas);
  canvas.restore();
 }

}

然后直接調(diào)用該方法格式化:

mTextView.setText(formatWeiBoContent(this,mTextView.getText().toString(),mTextView))

最終的效果圖和文章開(kāi)頭效果一樣了,并且可以點(diǎn)擊,這里展示了點(diǎn)擊”網(wǎng)頁(yè)鏈接”時(shí)彈出的 Toast 提示:

總結(jié)

本文僅介紹了 SpannableString 常用的一些場(chǎng)景,例如修改特定文本的顏色,替換特定文本,特定文本的點(diǎn)擊事件,但是 SpannableString 的強(qiáng)大遠(yuǎn)不止如此。SpannableString 的更多用法可閱讀官方文檔。好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • Android入門(mén)之Gallery用法實(shí)例解析

    Android入門(mén)之Gallery用法實(shí)例解析

    這篇文章主要介紹了Android入門(mén)之Gallery用法,對(duì)Android初學(xué)者有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2014-08-08
  • 深入理解Android中View繪制的三大流程

    深入理解Android中View繪制的三大流程

    這篇文章主要給大家介紹了關(guān)于Android中View繪制的三大流程,View的工作流程主要是指measure、layout、draw這三大流程,即測(cè)量、布局和繪制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2017-07-07
  • 解析Android框架之OkHttp3源碼

    解析Android框架之OkHttp3源碼

    OkHttp3是一個(gè)處理網(wǎng)絡(luò)請(qǐng)求的開(kāi)源項(xiàng)目,是安卓端最火熱的輕量級(jí)框架。本文將詳細(xì)解析它的源碼。
    2021-06-06
  • Android如何讓APP無(wú)法在指定的系統(tǒng)版本上運(yùn)行(實(shí)現(xiàn)方法)

    Android如何讓APP無(wú)法在指定的系統(tǒng)版本上運(yùn)行(實(shí)現(xiàn)方法)

    這篇文章主要介紹了Android如何讓APP無(wú)法在指定的系統(tǒng)版本上運(yùn)行(實(shí)現(xiàn)方法),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • Android RecyclerView網(wǎng)格布局(支持多種分割線(xiàn))詳解(2)

    Android RecyclerView網(wǎng)格布局(支持多種分割線(xiàn))詳解(2)

    這篇文章主要為大家詳細(xì)介紹了Android RecyclerView網(wǎng)格布局,支持多種分割線(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-02-02
  • Flutter路由框架Fluro使用教程詳細(xì)講解

    Flutter路由框架Fluro使用教程詳細(xì)講解

    在Flutter應(yīng)用開(kāi)發(fā)過(guò)程中,除了使用Flutter官方提供的路由外,還可以使用一些第三方路由框架來(lái)實(shí)現(xiàn)頁(yè)面管理和導(dǎo)航,F(xiàn)luro作為一款優(yōu)秀的Flutter企業(yè)級(jí)路由框架,F(xiàn)luro的使用比官方提供的路由框架要復(fù)雜一些,但是卻非常適合中大型項(xiàng)目
    2022-10-10
  • Android自定義View實(shí)現(xiàn)LayoutParams的方法詳解

    Android自定義View實(shí)現(xiàn)LayoutParams的方法詳解

    這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)LayoutParams,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-02-02
  • 詳解Android studio中正確引入so文件的方法

    詳解Android studio中正確引入so文件的方法

    本篇文章主要介紹了Android studio中正確引入so文件的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • Android開(kāi)發(fā)之BottomSheetDialog組件的使用

    Android開(kāi)發(fā)之BottomSheetDialog組件的使用

    BottomSheetDialog是底部操作控件,可在屏幕底部創(chuàng)建一個(gè)支持滑動(dòng)關(guān)閉視圖。本文將通過(guò)示例詳細(xì)講解它的使用,感興趣的小伙伴可以了解一下
    2023-01-01
  • 詳解Android中一些SQLite的增刪改查操作

    詳解Android中一些SQLite的增刪改查操作

    這篇文章主要為大家詳細(xì)介紹了Android中一些SQLLite的增刪改查操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-05-05

最新評(píng)論

南阳市| 乌拉特后旗| 黄浦区| 宣化县| 酉阳| 克拉玛依市| 佛教| 遵化市| 太仆寺旗| 冕宁县| 桐梓县| 松江区| 东山县| 桑植县| 阿拉善右旗| 梅州市| 绥宁县| 定西市| 黄山市| 禄劝| 同德县| 青海省| 盐池县| 广宁县| 延长县| 宁都县| 陇西县| 东阿县| 尼勒克县| 高要市| 通辽市| 加查县| 邓州市| 昭苏县| 沧州市| 嘉祥县| 屏南县| 通山县| 自贡市| 济南市| 邵阳县|