先让大家看下效果图,这个是我自己在闲暇时间仿写的新浪微博客户端:
今天来讲讲如何实现上图的效果,这里需要用到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 MyClickableSpan clickableSpan = new MyClickableSpan() { 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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!