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

前言

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

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

f112f41d38dda93f9f6ff4cb25bee158.png

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

要实现的效果:

将话题进行变色并且可以点击提示对应的话题文本内容

将图片表情替换掉对应的表情关键字显示

将链接地址替换成一个链接的图片和”网页链接”四个字显示

将@的用户进行变色并且可以点击提示对应的话题文本内容

需要:

使用正则表达式提取文本内对应的”话题”、”表情”、”网页链接”、以及”@用户”内容

使用 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 LinkedHashMap 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 提示:

ba0bd5b099b020e2a8793f67cdeb6e40.png

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/485534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2021年中国AIoT产业全景图谱

来源&#xff1a;物联网智库&#xff08;iot101&#xff09;编辑&#xff1a;蒲蒲日前&#xff0c;在“2020 AIoT产业年终盛典”上&#xff0c;物联网智库正式发布全新升级版的《2021中国AIoT产业全景图谱报告》&#xff08;以下简称“报告”&#xff09;。据悉&#xff0c;这是…

统计学权威盘点过去50年最重要的统计学思想,因果推理、bootstrap等上榜,Judea Pearl点赞...

来源&#xff1a;AI科技评论作者&#xff1a;陈彩娴、Mr Bear编辑&#xff1a;青暮近日&#xff0c;图灵奖得主、“贝叶斯网络之父”Judea Pearl在Twitter上分享了一篇新论文“What are the most important statistical ideas of the past 50 years?”&#xff08;过去50年中最…

自研芯片鸿蒙系统,华为智慧屏:自研智慧芯片鸿蒙系统 9月发布

7月26日消息 今日&#xff0c;华为在深圳举办媒体沟通会。华为消费者业务CEO余承东公布了华为智慧屏战略。华为全场景智慧化战略采用“18N”三圈层结构&#xff0c;覆盖家庭、办公、车载与运动四大场景&#xff0c;并以手机带动平板、PC、音箱、耳机等8个辅入口及更多外围生态设…

js下载文件

HTML与文件下载 如果希望在前端直接出发某些资源的下载&#xff0c;最方便快捷的方法就是使用HTML5原生的download属性&#xff0c; 例如&#xff1a; <a href"large.jpg" download>下载</a> 但显然&#xff0c;如果纯粹利用HTML属性来实现文件的下载&am…

二十世纪的十大科学骗局

来源&#xff1a;扬子晚报一般来说科学本是最讲求真实性的&#xff0c;但近些年来所谓轰动一时的科学“成果”中&#xff0c;却颇多假冒伪劣产品。1、百慕大“死亡三角”在众多科学骗局中&#xff0c;“百慕大”传说是影响最大且流传最广的一例&#xff0c;但它的起因只是第二次…

html 文本框 初始化,Flutter 文本框初始化时显示默认值

刚开始作Flutter文本框时候&#xff0c;使用的是TextField。彷佛大多数状况下都没有问题。代码形式以下&#xff1a;htmlclass _FooState extends State {TextEditingController _controller;overridevoid initState() {super.initState();_controller new TextEditingControl…

复杂性系统面临的难题

来源&#xff1a;人机与认知实验室1 什么是复杂性目前无法表述清楚在汉语中“复杂”一词的意思为“事物的种类、头绪等多而杂”。在《朗文当代英语词典》中&#xff0c;形容词complex被解释为&#xff1a;(1)难于理解、解释或处理&#xff0c;不清楚或不简单&#xff1b; (2)由…

MySQL优化(2)--------常用优化

前言 之前已经简单介绍了MySQL的优化步骤&#xff0c;那么接下来自然而是就是常用的SQL优化&#xff0c;比如inseer、group by等常用SQL的优化&#xff0c;会涉及SQL语句内部细节&#xff08;这正是我缺乏的&#xff09;。最后希望自己能记录完成的一套MySQL优化博文&#xff0…

码元,波特,速率,带宽

码元&#xff1a;一个固定时长的信号波形&#xff0c;代表不同离散数值的基本波形 1码元可以携带多个比特的信息量 例如&#xff1a;下图就称为二进制码元&#xff0c;因为只有两种状态&#xff0c;一种代表0状态&#xff0c;一种代表1状态 还有其他进制码元 时长称为码元宽…

大爆炸之前的宇宙是什么样子?|赠书

来源&#xff1a;科研圈宇宙蛋难题古代的创世神话往往表现出奇妙的独创性&#xff0c;但是追根究底&#xff0c;它们只有两个基本的选择&#xff1a;宇宙要么是在有限的时间以前被创造的&#xff0c;要么就是永恒存在的。以下是神圣的印度教经文《奥义书》中描述的场景&#xf…

js获取html样式属性,js怎么获取指定css属性的值?

js怎么获取指定css属性的值&#xff1f;下面本篇文章给大家介绍一下。有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对大家有所帮助。js怎么获取指定css属性的值&#xff1f;1、通过dom.style.属性 来获取但是这种方法无法获取id、class里的属性例子:…

奈氏准则,香农定理

失真 现实中的信道(带宽受限&#xff0c;有噪声&#xff0c;干扰)&#xff0c;导致一些信号发生问题 影响失真的因素&#xff1a; 码元传输速率&#xff08;越快&#xff0c;失真越严重&#xff09; 信号传输距离&#xff08;越远越严重&#xff09; 噪声干扰 传输媒体质…

IBM中国研究院被曝已全面关闭

来源&#xff1a;AI科技评论作者&#xff1a;青暮AI科技评论最新消息&#xff0c;网传IBM中国研究院&#xff08;IBM CRL&#xff09;已经全面关闭。AI科技评论求证圈内权威人士&#xff0c;其表示消息大概率属实。IBM研究院是IBM公司的一个&#xff08;研究&#xff09;部门&a…

图形化安装配置:安装oracle、新建数据库、用plsql连接oracle,套路明白了其实挺简单...

1&#xff1a;安装oracle。 我的安装路径是c:\app&#xff0c;那oracle_home就是&#xff1a;C:\app\guestAdmin\product\11.2.0\dbhome_1。 装完后菜单在这里&#xff1a; 2&#xff1a;创建数据库his&#xff0c;注意&#xff1a;oracle的每个数据库&#xff0c;都是一个服务…

细数高光时刻,2020全球科技巨头如何激战AI?

来源&#xff1a;嵌入式资讯精选本文作者&#xff1a;Jiachang Pan编辑&#xff1a;SV Insight最近&#xff0c;百度、谷歌等多家科技巨头相继发布2020年AI发展总结。2020年12月30日&#xff0c;百度以一篇万字长文《百度AI的2020》向2020年告别。2021年1月12日&#xff0c;谷歌…

数据编码信号调制

信道上传送的信号也可以分为&#xff1a; 基带信号&#xff1a;将数字信号1&#xff0c;0直接用两种不同的电压表示&#xff0c;再送到数字信道上去传输 直接表达了要输出的信息的信号。 基带信号在数字信道上去传输&#xff0c;就叫做基带传输 宽带信号&#xff1a;将基带…

人为什么要学数学 ——数学意义的哲学思考

来源&#xff1a;算法与数学之美编辑 ∑Gemini人为什么要学数学&#xff1f;其实很多人并不清楚&#xff0c;甚至存在许多认识误区&#xff0e;有学生认为&#xff0c;“数学除了买东西的时候有点用&#xff0c;考试的时候有点用&#xff0c;没有多大的实际用途&#xff0e;”还…

html标签始终在右下角,html+javascript实现图片始终在页面右下角

标题页function setVariables() {imgwidth235; //图像的宽度imgheight19; //图像的高度if (navigator.appName "Netscape") { //netscape下的位置设置horz".left";vert".top";docStyle"document.";styleDoc"";innerW"…

Gary Marcus:AI 可以从人类思维中学习的11个启示

来源&#xff1a;AI科技评论作者&#xff1a;Gary Marcus、Ernest Davis编译&#xff1a;陈彩娴1969 年图灵奖得主、MIT 人工智能实验室创始人马文明斯基&#xff08;Marvin Minsky&#xff09;在其1986年著作《心智社会》&#xff08;The Society of Mind&#xff09;一书中曾…

我们的大脑,足以理解大脑本身吗?

来源&#xff1a; 利维坦树突&#xff08;红色&#xff09;神经元的分支过程&#xff0c;接收突触信息的突出棘的渲染&#xff0c;以及来自小鼠大脑皮层的饱和重建&#xff08;多色圆柱体&#xff09;。© Lichtman Lab at Harvard University利维坦按&#xff1a;关于大脑…