十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
这篇文章将为大家详细讲解有关怎么在Android中利用SpannableString对内容进行格式化,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
目前成都创新互联公司已为上千余家的企业提供了网站建设、域名、虚拟主机、网站托管维护、企业网站设计、溪湖网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
要实现的效果:
将话题进行变色并且可以点击提示对应的话题文本内容
将图片表情替换掉对应的表情关键字显示
将链接地址替换成一个链接的图片和”网页链接”四个字显示
将@的用户进行变色并且可以点击提示对应的话题文本内容
需要:
使用正则表达式提取文本内对应的”话题”、”表情”、”网页链接”、以及”@用户”内容
使用 SpannableString 格式化提取到的文本
给格式化的部分添加点击事件
定义正则表达式
首先定义”话题”、”表情”、”网页链接”、以及”@用户”对应的正则表达式和对应的 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 LinkedHashMapsMap; 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))
关于怎么在Android中利用SpannableString对内容进行格式化就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。