欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android利用SpannableString实现格式化微博内容

程序员文章站 2023-11-29 13:37:16
前言 在android开发中,有许多信息展示需要通过textview来展现,如果只是普通的信息展现,使用textview settext(charsequence str...

前言

在android开发中,有许多信息展示需要通过textview来展现,如果只是普通的信息展现,使用textview settext(charsequence str)设置即可,但是当在textview里的这段内容需要截取某一部分字段,可以被点击以及响应响应的操作,这时候就需要用到spannablestring了,spannablestring 配合 textview 可以轻松实现对特定的文本做特定处理,例如可以修改文字颜色、背景色、将文字替换为图片实现,点击效果等。

首先看看最终实现的效果图:

Android利用SpannableString实现格式化微博内容

第一个卡片内的微博是原始文本信息,第二个卡片内的微博是第一个格式化后的文本内容,将微博内的”话题”、”表情”、”网页链接”、以及”@用户”都进行了处理,并可以点击,使其和官方微博展示的样式保持一致。

要实现的效果:

  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 = "\\[(\\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 提示:

Android利用SpannableString实现格式化微博内容

总结

本文仅介绍了 spannablestring 常用的一些场景,例如修改特定文本的颜色,替换特定文本,特定文本的点击事件,但是 spannablestring 的强大远不止如此。spannablestring 的更多用法可阅读官方文档。好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。