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

前言

在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 = "\\[(\\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 的更多用法可阅读官方文档。好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android TextView中文字通过SpannableString设置属性用法示例

    本文实例讲述了Android TextView中文字通过SpannableString设置属性的方法.分享给大家供大家参考,具体如下: 在Android中,TextView是我们最常用的用来显示文本的控件. 一般情况下,TextView中的文本都是一个样式.那么如何对于TextView中各个部分的文本来设置字体,大小,颜色,样式,以及超级链接等属性呢?下面我们通过SpannableString的具体实例操作来演示一下. //创建一个 SpannableString对象 SpannableStri

  • TextView使用SpannableString设置复合文本 SpannableString实现TextView的链接效果

    一.简介 TextView使用SpannableString设置复合文本 TextView通常用来显示普通文本,但是有时候需要对其中某些文本进行样式.事件方面的设置.Android系统通过SpannableString类来对指定文本进行相关处理,具体有以下功能: 1.BackgroundColorSpan 背景色 2.ClickableSpan 文本可点击,有点击事件 3.ForegroundColorSpan 文本颜色(前景色) 4.MaskFilterSpan 修饰效果,如模糊(BlurMa

  • Android SpannableString设置超链接、颜色、字体等属性

    Android SpannableString设置超链接.颜色.字体等属性 在Android中,TextView是我们最常用的用来显示文本的控件. 一般情况下,TextView中的文本都是一个样式.那么如何对于TextView中各个部分的文本来设置字体,大小,颜色,样式,以及超级链接等属性呢?下面我们通过SpannableString的具体实例操作来演示一下. package com.snowdream; import java.io.IOException; import org.xmlpul

  • Android中的SpannableString与SpannableStringBuilder详解

    前言 最近在学习Android开发,发现确实有太多东西需要去整理,去学习.慢慢来吧,任何东东的深入学习都是不简单的.今天稍微整理下SpannableString与SpannableStringBuilder,因为在上篇中,我们曾讲到过有关CharSequence的东东,有关CharSequence的话,这两个类是必谈的,所以这里加以整理. 一.概述 1.SpannableString.SpannableStringBuilder与String的关系 首先SpannableString.Spann

  • Android TextView使用SpannableString设置复合文本的方法详解

    本文实例讲述了Android TextView使用SpannableString设置复合文本的方法.分享给大家供大家参考,具体如下: TextView通常用来显示普通文本,但是有时候需要对其中某些文本进行样式.事件方面的设置.Android系统通过SpannableString类来对指定文本进行相关处理,具体有以下功能: 1.BackgroundColorSpan 背景色 2.ClickableSpan 文本可点击,有点击事件 3.ForegroundColorSpan 文本颜色(前景色) 4.

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

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

  • Android利用ContentProvider读取短信内容

    本文实例为大家分享了Android利用ContentProvider读取短信内容的具体代码,供大家参考,具体内容如下 首先,我们来看下运行效果 运行效果如下: 展示短信内容的效果如下: 布局文件(activity_sms.xml) <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/an

  • Python利用prettytable实现格式化输出内容

    目录 楔子 添加表头.添加行.添加列 输出指定行.指定列 设置表格样式 设置对齐方式 设置边框样式 楔子 我们用 MySQL 客户端查询数据的时候,是以下面这种格式显示的: 内容展示的非常漂亮,而 Python 有一个第三方模块叫 prettytable,专门用来将数据以上面这种格式输出,我们来看一下用法. 添加表头.添加行.添加列 类似于数据库中的表,由表头(或者说字段名),以及每一行的内容组成. from prettytable import PrettyTable # 传入的 name.a

  • Android利用startActivityForResult返回数据到前一个Activity

    在Android里面,从一个Activity跳转到另一个Activity.再返回,前一个Activity默认是能够保存数据和状态的.但这次我想通过利用startActivityForResult达到相同的目的,虽然看起来变复杂了,但可以探索下startActivityForResult背后的原理和使用注意事项. 要实现的功能如下: 从Activity A将数据传到Activity B,再从Activity B中获取数据后,再传回Activity A.在Activity B中添加一个"回到上一页&

  • 利用Python爬取微博数据生成词云图片实例代码

    前言 在很早之前写过一篇怎么利用微博数据制作词云图片出来,之前的写得不完整,而且只能使用自己的数据,现在重新整理了一下,任何的微博数据都可以制作出来,一年一度的虐汪节,是继续蹲在角落默默吃狗粮还是主动出击告别单身汪加入散狗粮的行列就看你啦,七夕送什么才有心意,程序猿可以试试用一种特别的方式来表达你对女神的心意.有一个创意是把她过往发的微博整理后用词云展示出来.本文教你怎么用Python快速创建出有心意词云,即使是Python小白也能分分钟做出来.下面话不多说了,来一起看看详细的介绍吧. 准备工作

  • Android利用BitMap获得图片像素数据的方法

    本文实例讲述了Android利用BitMap获得图片像素数据的方法.分享给大家供大家参考,具体如下: 网上看到的参考是: int[] pixels = new int[bit.getWidth()*bit.getHeight()];//保存所有的像素的数组,图片宽×高 bit.getPixels(pixels,0,bit.getWidth(),0,0,bit.getWidth(),bit.getHeight()); for(int i = 0; i < pixels.length; i++){

  • Android编程之创建自己的内容提供器实现方法

    本文实例讲述了Android编程之创建自己的内容提供器实现方法.分享给大家供大家参考,具体如下: 我们学习了如何在自己的程序中访问其他应用程序的数据.总体来说思 路还是非常简单的,只需要获取到该应用程序的内容 URI,然后借助 ContentResolver 进行CRUD 操作就可以了.可是你有没有想过,那些提供外部访问接口的应用程序都是如何实现这种功能的呢?它们又是怎样保证数据的安全性,使得隐私数据不会泄漏出去? 创建内容提供器的步骤 前面已经提到过,如果想要实现跨程序共享数据的功能,官方推荐

  • Android利用悬浮按钮实现翻页效果

    今天给大家分享下自己用悬浮按钮点击实现翻页效果的例子. 首先,一个按钮要实现悬浮,就要用到系统顶级窗口相关的WindowManager,WindowManager.LayoutParams.那么在AndroidManifest.xml中添加权限: <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 然后,我们要对WindowManager,WindowManager.Layout

  • Android实现EditText控件禁止输入内容的方法(附测试demo)

    本文实例讲述了Android实现EditText控件禁止输入内容的方法.分享给大家供大家参考,具体如下: 问题: android如何实现EditText控件禁止往里面输入内容? 修改版解决方法: EditText editText = (EditText) findViewById(R.id.editText1); editText.setKeyListener(null); 看到这个问题大家可能有点奇怪了,EditText的功能不就是往上面写入内容吗? 再者,如果真要禁止输入文本,在布局文件中

  • Android实现的数字格式化用法示例

    本文实例讲述了Android实现的数字格式化用法.分享给大家供大家参考,具体如下: package formatnumber; import java.text.DecimalFormat; public class FormatNumber { public static void main(String[] args) { DecimalFormat df = new DecimalFormat(); double data = 1234.56789; System.out.println(

随机推荐