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

首頁 > 系統 > Android > 正文

Android利用SpannableString實現格式化微博內容

2019-12-12 03:26:03
字體:
來源:轉載
供稿:網友

前言

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

首先看看最終實現的效果圖:

第一個卡片內的微博是原始文本信息,第二個卡片內的微博是第一個格式化后的文本內容,將微博內的”話題”、”表情”、”網頁鏈接”、以及”@用戶”都進行了處理,并可以點擊,使其和官方微博展示的樣式保持一致。

要實現的效果:

  1. 將話題進行變色并且可以點擊提示對應的話題文本內容
  2. 將圖片表情替換掉對應的表情關鍵字顯示
  3. 將鏈接地址替換成一個鏈接的圖片和”網頁鏈接”四個字顯示
  4. 將@的用戶進行變色并且可以點擊提示對應的話題文本內容

需要:

  1. 使用正則表達式提取文本內對應的”話題”、”表情”、”網頁鏈接”、以及”@用戶”內容
  2. 使用 SpannableString 格式化提取到的文本
  3. 給格式化的部分添加點擊事件

定義正則表達式

首先定義”話題”、”表情”、”網頁鏈接”、以及”@用戶”對應的正則表達式和對應的 Pattern。SCHEME 下文會提到具體的用處的。

public class WeiboPattern { // #話題# public static final String REGEX_TOPIC = "#[//p{Print}//p{InCJKUnifiedIdeographs}&&[^#]]+#"; // [表情] public static final String REGEX_EMOTION = "http://[(//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 格式化

我將此過程寫到一個方法內了,下面直接上代碼,代碼中有詳細的注釋解釋:

/** * 格式化微博文本 * * @param context 上下文 * @param source 源文本 * @param textView 目標 TextView * @return SpannableStringBuilder */public static SpannableStringBuilder formatWeiBoContent(Context context, String source, TextView textView) { // 獲取到 TextView 的文字大小,后面的 ImageSpan 需要用到該值 int textSize = (int) textView.getTextSize(); // 若要部分 SpannableString 可點擊,需要如下設置 textView.setMovementMethod(LinkMovementMethod.getInstance()); // 將要格式化的 String 構建成一個 SpannableStringBuilder SpannableStringBuilder value = new SpannableStringBuilder(source); // 使用正則匹配話題 Linkify.addLinks(value, WeiboPattern.PATTERN_TOPIC, WeiboPattern.SCHEME_TOPIC); // 使用正則匹配鏈接 Linkify.addLinks(value, WeiboPattern.PATTERN_URL, WeiboPattern.SCHEME_URL); // 使用正則匹配@用戶 Linkify.addLinks(value, WeiboPattern.PATTERN_AT, WeiboPattern.SCHEME_AT); // 自定義的匹配部分的點擊效果 MyClickableSpan clickSpan; // 獲取上面到所有 addLinks 后的匹配部分(這里一個匹配項被封裝成了一個 URLSpan 對象) URLSpan[] urlSpans = value.getSpans(0, value.length(), URLSpan.class); // 遍歷所有的 URLSpan for (final URLSpan urlSpan : urlSpans) { // 點擊匹配部分效果  clickSpan = new MyClickableSpan() {   @Override   public void onClick(View view) {    ToastUtils.makeShort(urlSpan.getURL());   }  };  // 話題  if (urlSpan.getURL().startsWith(WeiboPattern.SCHEME_TOPIC)) {   int start = value.getSpanStart(urlSpan);   int end = value.getSpanEnd(urlSpan);   value.removeSpan(urlSpan);   // 格式化話題部分文本   value.setSpan(clickSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  }  // @用戶  if (urlSpan.getURL().startsWith(WeiboPattern.SCHEME_AT)) {   int start = value.getSpanStart(urlSpan);   int end = value.getSpanEnd(urlSpan);   value.removeSpan(urlSpan);   // 格式化@用戶部分文本   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);  } } // 表情需要單獨格式化 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 ,可解決默認的 ImageSpan 不垂直居中的問題   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(" 網頁鏈接"); return builder;}

getUrlTextSpannableString() :方法是用來返回一個圖標+”網頁鏈接” SpannableString,用于替換鏈接文本

上面將”話題”、”表情”、”網頁鏈接”都用了addLinks方法來標記的,然后統一處理。表情則是單獨處理的。

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

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

還有剛才說到的自定義 MyClickableSpan 修改默認的樣式:

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

另外,由于默認的 ImageSpan 在 TextView 有使用android:lineSpacingExtra屬性時,不會垂直居中,所以使用到了網上的一個繼承自 ImageSpan 的 VerticalImageSpan 可以做到保持圖片在 TextView 內保持垂直居中:

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

然后直接調用該方法格式化:

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

最終的效果圖和文章開頭效果一樣了,并且可以點擊,這里展示了點擊”網頁鏈接”時彈出的 Toast 提示:

總結

本文僅介紹了 SpannableString 常用的一些場景,例如修改特定文本的顏色,替換特定文本,特定文本的點擊事件,但是 SpannableString 的強大遠不止如此。SpannableString 的更多用法可閱讀官方文檔。好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對武林網的支持。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 光泽县| 湘乡市| 沂水县| 临西县| 霸州市| 民勤县| 兴业县| 嫩江县| 连平县| 乃东县| 裕民县| 铁岭县| 沈丘县| 从化市| 全椒县| 锡林郭勒盟| 札达县| 常山县| 海晏县| 马边| 平远县| 龙胜| 邻水| 宁国市| 垣曲县| 珠海市| 讷河市| 连平县| 朝阳市| 仁化县| 竹溪县| 饶平县| 来安县| 交口县| 井陉县| 重庆市| 新和县| 休宁县| 农安县| 宁德市| 休宁县|