安卓开发笔记——高仿新浪微博文字处理(实现关键字高亮,自定义表情替换并加入点击事件实现)

先让大家看下效果图,这个是我自己在闲暇时间仿写的新浪微博客户端:

  

今天来讲讲如何实现上图的效果,这里需要用到SpannableString这个工具类,如果你对这个类并不熟悉,可以先看下我之前写的2篇文章:

《安卓开发笔记——个性化TextView(新浪微博)》:http://www.cnblogs.com/lichenwei/p/4411607.html

《安卓开发笔记——丰富多彩的TextView》:http://www.cnblogs.com/lichenwei/p/4612079.html

先来说下关于新浪微博消息的结构,在获取新浪微博消息的时候,我们会发现这几个东西:

话题:以##为收尾,例如#世界读书日#等。

At:以@开头,空格结尾,例如@新浪微博 等。

网址:以http://开头,例如http://www.baidu.com/。

表情:以[]为收尾,例如[微笑]、[哈哈]等。

在一段140字的文本中要找出上面这些关键字,无疑就是用到正则表达式了,在这里我定义了4个正则表达式:

    // 定义正则表达式
    private static final String AT = "@[\u4e00-\u9fa5\\w]+";// @人
    private static final String TOPIC = "#[\u4e00-\u9fa5\\w]+#";// ##话题
    private static final String EMOJI = "\\[[\u4e00-\u9fa5\\w]+\\]";// 表情
    private static final String URL = "http://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";// url

然后根据我上2篇文章提供的正则匹配法去匹配微博消息的文本字段,在这里有些技巧和大家分享下:

  最早我在实现这个功能的时候用很多个while循环,先匹配@,再匹配话题,再匹配表情最后匹配Url,这样子在实现层面上固然没什么问题,但是我们写程序还是需要注意代码的运行效率的,这里给大家分享些技巧:

  1、我们可以先把我们想匹配的正则拼接起来用"|"分割开,然后把每一部分的正则用小括号关联起来,然后可以在group方法里设置索引来定位匹配到正则内容,索引0代表全部,1代表第一个括号,以此类推。

  2、关于ClickableSpan实现可以点击样式,这里我们需要做2个处理

  (1)、在ClickableSpan的源码里我们可以发现这样的2句话(如下图),它是带有自定颜色和文字下划线的

所以这里我们需要对它做下处理,我们去继承这个类然后复写它的方法,定义我们自己想要的颜色和样式就可以了。

 1     /**
 2      * 继承ClickableSpan复写updateDrawState方法,自定义所需样式
 3      * @author Rabbit_Lee
 4      *
 5      */
 6     public static class MyClickableSpan extends ClickableSpan {
 7
 8         @Override
 9         public void onClick(View widget) {
10
11         }
12
13         @Override
14         public void updateDrawState(TextPaint ds) {
15             super.updateDrawState(ds);
16             ds.setColor(Color.BLUE);
17             ds.setUnderlineText(false);
18         }
19
20     }

  (2)、由于@昵称、#话题#、http://等这些关键字是可以点击的,所以我们需要对TextView做一些处理,需要去设置它的MovementMethod,具体看下面代码。

  3、在匹配表情(下文会提到),这里需要注意的一个地方是当我们获取到了Bitmap对象的时候需要对这个对象进行压缩处理,要使得它的尺寸大小和我们的TextView的字体大小一致。

  

 1    /**
 2      * 设置微博内容样式
 3      * @param context
 4      * @param source
 5      * @param textView
 6      * @return
 7      */
 8     public static SpannableString getWeiBoContent(final Context context, String source, TextView textView) {
 9         SpannableString spannableString = new SpannableString(source);
10
11         //设置正则
12         Pattern pattern = Pattern.compile(REGEX);
13         Matcher matcher = pattern.matcher(spannableString);
14
15         if (matcher.find()) {
16             // 要实现文字的点击效果,这里需要做特殊处理
17             textView.setMovementMethod(LinkMovementMethod.getInstance());
18             // 重置正则位置
19             matcher.reset();
20         }
21
22         while (matcher.find()) {
23             // 根据group的括号索引,可得出具体匹配哪个正则(0代表全部,1代表第一个括号)
24             final String at = matcher.group(1);
25             final String topic = matcher.group(2);
26             String emoji = matcher.group(3);
27             final String url = matcher.group(4);
28
29             // 处理@符号
30             if (at != null) {
31                 //获取匹配位置
32                 int start = matcher.start(1);
33                 int end = start + at.length();
34                 MyClickableSpan clickableSpan = new MyClickableSpan() {
35
36                     @Override
37                     public void onClick(View widget) {
38                         //这里需要做跳转用户的实现,先用一个Toast代替
39                         Toast.makeText(context, "点击了用户:" + at, Toast.LENGTH_LONG).show();
40                     }
41                 };
42                 spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
43             }
44
45             // 处理话题##符号
46             if (topic != null) {
47                 int start = matcher.start(2);
48                 int end = start + topic.length();
49                 ClickableSpan clickableSpan = new ClickableSpan() {
50
51                     @Override
52                     public void onClick(View widget) {
53                         Toast.makeText(context, "点击了话题:" + topic, Toast.LENGTH_LONG).show();
54                     }
55                 };
56                 spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
57             }
58
59             if (emoji != null) {
60                 int start = matcher.start(3);
61                 int end = start + emoji.length();
62                 int ResId = EmotionUtils.getImgByName(emoji);
63                 Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), ResId);
64                 if (bitmap != null) {
65                     // 获取字符的大小
66                     int size = (int) textView.getTextSize();
67                     // 压缩Bitmap
68                     bitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
69                     // 设置表情
70                     ImageSpan imageSpan = new ImageSpan(context, bitmap);
71                     spannableString.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
72                 }
73             }
74
75             // 处理url地址
76             if (url != null) {
77                 int start = matcher.start(4);
78                 int end = start + url.length();
79                 MyClickableSpan clickableSpan = new MyClickableSpan() {
80
81                     @Override
82                     public void onClick(View widget) {
83                         Toast.makeText(context, "点击了网址:" + url, Toast.LENGTH_LONG).show();
84                     }
85                 };
86                 spannableString.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
87             }
88         }
89
90         return spannableString;
91     }

  再来说下关于表情替换的实现,首先我们需要把表情我们需要的表情下载下来存在我们的资源文件中,由于微博信息返回的是[XX],这里的XX是中文而我们的资源文件的命名不可以是中文,所以我们这边可以写一个资源存储类,用一个静态的Map集合去封装,把中文当成Key去对应Int类型的资源ID。

 1 package com.lcw.weibo.utils;
 2
 3 import java.io.Serializable;
 4 import java.util.HashMap;
 5 import java.util.Map;
 6
 7 import com.lcw.weibo.R;
 8
 9 @SuppressWarnings("serial")
10 public class EmotionUtils implements Serializable {
11
12     public static Map<String, Integer> emojiMap;
13
14     static {
15         emojiMap = new HashMap<String, Integer>();
16         emojiMap.put("[微笑]", R.drawable.d_hehe);
17         emojiMap.put("[呵呵]", R.drawable.d_hehe);
18         emojiMap.put("[嘻嘻]", R.drawable.d_xixi);
19         emojiMap.put("[哈哈]", R.drawable.d_haha);
20         emojiMap.put("[爱你]", R.drawable.d_aini);
21         emojiMap.put("[挖鼻屎]", R.drawable.d_wabishi);
22         emojiMap.put("[吃惊]", R.drawable.d_chijing);
23         emojiMap.put("[晕]", R.drawable.d_yun);
24         emojiMap.put("[泪]", R.drawable.d_lei);
25         emojiMap.put("[馋嘴]", R.drawable.d_chanzui);
26         emojiMap.put("[抓狂]", R.drawable.d_zhuakuang);
27         emojiMap.put("[哼]", R.drawable.d_heng);
28         emojiMap.put("[可爱]", R.drawable.d_keai);
29         emojiMap.put("[怒]", R.drawable.d_nu);
30         emojiMap.put("[汗]", R.drawable.d_han);
31         emojiMap.put("[害羞]", R.drawable.d_haixiu);
32         emojiMap.put("[心]", R.drawable.emoji_0x2764);
33         emojiMap.put("[睡觉]", R.drawable.d_shuijiao);
34         emojiMap.put("[钱]", R.drawable.d_qian);
35         emojiMap.put("[偷笑]", R.drawable.d_touxiao);
36         emojiMap.put("[笑cry]", R.drawable.d_xiaoku);
37         emojiMap.put("[doge]", R.drawable.d_doge);
38         emojiMap.put("[喵喵]", R.drawable.d_miao);
39         emojiMap.put("[酷]", R.drawable.d_ku);
40         emojiMap.put("[衰]", R.drawable.d_shuai);
41         emojiMap.put("[闭嘴]", R.drawable.d_bizui);
42         emojiMap.put("[鄙视]", R.drawable.d_bishi);
43         emojiMap.put("[花心]", R.drawable.d_huaxin);
44         emojiMap.put("[鼓掌]", R.drawable.d_guzhang);
45         emojiMap.put("[悲伤]", R.drawable.d_beishang);
46         emojiMap.put("[思考]", R.drawable.d_sikao);
47         emojiMap.put("[生病]", R.drawable.d_shengbing);
48         emojiMap.put("[亲亲]", R.drawable.d_qinqin);
49         emojiMap.put("[怒骂]", R.drawable.d_numa);
50         emojiMap.put("[太开心]", R.drawable.d_taikaixin);
51         emojiMap.put("[懒得理你]", R.drawable.d_landelini);
52         emojiMap.put("[右哼哼]", R.drawable.d_youhengheng);
53         emojiMap.put("[左哼哼]", R.drawable.d_zuohengheng);
54         emojiMap.put("[嘘]", R.drawable.d_xu);
55         emojiMap.put("[委屈]", R.drawable.d_weiqu);
56         emojiMap.put("[吐]", R.drawable.d_tu);
57         emojiMap.put("[可怜]", R.drawable.d_kelian);
58         emojiMap.put("[打哈气]", R.drawable.d_dahaqi);
59         emojiMap.put("[挤眼]", R.drawable.d_jiyan);
60         emojiMap.put("[失望]", R.drawable.d_shiwang);
61         emojiMap.put("[顶]", R.drawable.d_ding);
62         emojiMap.put("[疑问]", R.drawable.d_yiwen);
63         emojiMap.put("[困]", R.drawable.d_kun);
64         emojiMap.put("[感冒]", R.drawable.d_ganmao);
65         emojiMap.put("[拜拜]", R.drawable.d_baibai);
66         emojiMap.put("[黑线]", R.drawable.d_heixian);
67         emojiMap.put("[阴险]", R.drawable.d_yinxian);
68         emojiMap.put("[打脸]", R.drawable.d_dalian);
69         emojiMap.put("[傻眼]", R.drawable.d_shayan);
70         emojiMap.put("[猪头]", R.drawable.d_zhutou);
71         emojiMap.put("[熊猫]", R.drawable.d_xiongmao);
72         emojiMap.put("[兔子]", R.drawable.d_tuzi);
73     }
74
75     public static int getImgByName(String imgName) {
76         Integer integer = emojiMap.get(imgName);
77         return integer == null ? -1 : integer;
78     }
79 }

关于这些表情资源要去哪里获取,大家可以去看下我之前的一篇文章《基于Java实现批量下载网络图片》:http://www.cnblogs.com/lichenwei/p/4610298.html

好了,到这里文章就结束了,有任何疑问或者建议,大家可以在文章评论给我留言。

作者:李晨玮
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!

时间: 2025-01-16 18:00:22

安卓开发笔记——高仿新浪微博文字处理(实现关键字高亮,自定义表情替换并加入点击事件实现)的相关文章

安卓开发笔记——个性化TextView(新浪微博)

这几天在仿写新浪微博客户端,在处理微博信息的时候需要处理关键字高亮和微博表情,查了一些资料,决定记录点东西 先来看下效果图: 像以上这种#话题#,@XXX昵称,HTTP:网页链接等元素,在微博里是被高亮成蓝色效果的. 那么在我们的安卓程序开发中应该如何动态的实现这些效果呢? 其实很简单,我写了个小例子,先来看下效果图: 其实要实现这种效果非常的简单,在Android里已经帮我们封装好了一系列的工具类,例如: android.text.Spanned android.text.SpannableS

安卓开发笔记——丰富多彩的TextView

随手笔记,记录一些东西~ 记得之前写过一篇文章<安卓开发笔记——个性化TextView(新浪微博)>:http://www.cnblogs.com/lichenwei/p/4411607.html 文章里实现个性化TextView的主要方法是通过替换的方式,对关键字进行一些个性化处理,晚上再来补充一种实现方式. 老规矩,先看下效果图: 晚上带来的这种实现方式是通过Android官方给我们提供的Html类下面的fromHtml方法,这个方法可以对字符串进行HTML格式化,让TextView等一些

安卓开发笔记——多种方式实现底部菜单栏(仿微信界面)

关于底部菜单是什么,我想没必要介绍了,在市场上的APP里太常见了,这里提供两种方式来实现. 记得之前写过几篇关于底部菜单实现的方法,有兴趣的朋友可以看看: 1.<安卓开发复习笔记——TabHost组件(一)(实现底部菜单导航)> 2.<安卓开发复习笔记——TabHost组件(二)(实现底部菜单导航)> 3.<安卓开发笔记——Fragment+FragmentTabHost组件(实现新浪微博底部菜单)> 今天带来种相对更通俗易懂的写法,不再和过去一样去沿用TabHost了

安卓开发笔记——自定义广告轮播Banner(无限循环实现)

关于广告轮播,大家肯定不会陌生,它在现手机市场各大APP出现的频率极高,它的优点在于"不占屏",可以仅用小小的固定空位来展示几个甚至几十个广告条,而且动态效果很好,具有很好的用户"友好性",下面来看几个示例图:     再来看下我仿写的效果: 关于广告轮播Banner这个东西,GitHub上面应该有现成的开源组件,不过我没去找过,觉得实现起来不会太难,就自己去仿写了,下面我说下实现的思路: 1.首先看到这个可以滑动切换图片的界面,我们很自然就会想到ViewPager

高仿新浪微博,写了个小demo

运用新浪开发的微博数据接口,花了两天时间高仿新浪iphone手机客户端的写了新特性和首页,感觉运用到的知识点还是挺多的,尤其实在框架的搭建和代码的封装上,直接决定了系统的重用性和扩展性,案例视图:                            共享几个案例中用到的小技巧: (1) 重写按钮的样式    一般按钮中的文字和图片都是左右排, 那么如何按照自己的想法,任意的控制按钮中元素的排列呢? 1 #import "XHcenterBtn.h" 2 #import "U

安卓开发笔记——打造属于自己的博客园APP(二)

在上一篇文章<安卓开发笔记——打造属于自己的博客园APP(一)>中,我们基本上实现了博客园的主体UI框架(后面可能会有些小变化,等遇到了再说).今天来讲讲博客园首页模块的大体实现,国际惯例,先来看下效果图: 整体UI效果: 下拉刷新和上拉加载的动画效果: 在上篇文章中,我们定义的Tabs主题文字分别是(首页,精华,候选,推荐),这边的命名我是根据博客园网站首页的栏目来命名的,那时候我还没仔细看过博客园的开放接口,后来才发现原来博客园没有对应开放这些栏目的接口,博客园只开放了(文章列表,48小时

安卓 开发笔记目录

安卓 开发笔记index 安卓基础 Fragment总结 安卓 BroadcastReceiver笔记 安卓 Notification-通知总结 开源框架笔记 安卓 okhttp小结 EventBus框架总结 安卓 图片加载框架ImageLoader 第三方SDK 安卓 短信验证MobSMS集成 开源项目 其他 安卓 常用属性设置代码笔记 安卓 代码混淆与打包 AOSP开发笔记 开发工具 SecureCRT连接Ubuntu配置 Ubuntu开发环境搭建 开发环境 AOSP android7.1.

安卓开发笔记——关于Handler的一些总结(上)

接上篇文章<安卓开发笔记——关于AsyncTask的使用>,今天来讲下在安卓开发里"重中之重"的另一个异步操作类Handler. 今天打算先讲下关于Handler的一些基本定义和使用方式 还是以一个下载图片为例,先看下实例效果: 好了,先来看下关于Handler的定义: 以上是官方对于Hanler类的描述,大致意思是说:Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息

微信小程序开发日记——高仿知乎日报(上)

本人对知乎日报是情有独钟,看我的博客和github就知道了,写了几个不同技术类型的知乎日报APP 要做微信小程序首先要对html,css,js有一定的基础,还有对微信小程序的API也要非常熟悉 我将该教程分为以下三篇 微信小程序开发日记--高仿知乎日报(上) 微信小程序开发日记--高仿知乎日报(中) 微信小程序开发日记--高仿知乎日报(下) 三篇分别讲不同的组件和功能块 这篇要讲 API分析 启动页 轮播图 日报列表 浮动按钮 侧滑菜单 API分析 以下是使用到的具体API,更加详细参数和返回结