Android中表情功能的完整处理方案

Posted by boredream on July 9, 2015

概述

  1. 原理和实现思路
  2. 表情图片显示
  3. 表情面板
  4. 表情的输入框插入和删除
  5. 表情添加脚本

正式介绍之前,先放demo截图 emoji1


原理和思路

表情内容的数据格式

表情看上去是图片,但是在数据传输的时候本质上是一个特殊文本 比如QQ表情就是一个 “/表情字母”的结构,比如害羞的表情就是/hx,呲牙就是/cy… 微博里表情就是”[表情名字]”的接口,比如可爱的表情就是[可爱]等等…

特殊文本显示图片的原理

需要用到安卓中的SpannableString拓展性字符串相关知识 SpannableString可以让一段字符串在显示的时候,将其中某小段文字附着上其他内容, 附着的拓展内容可能是图片,或者是文字格式,比如加粗斜体等

下面稍微展开介绍下SpannableString,会的可以跳过

常用的拓展内容包含有

  • BackgroundColorSpan 背景色
  • ClickableSpan 文本可点击,有点击事件
  • UnderlineSpan 下划线
  • ImageSpan 图片
  • StyleSpan 字体样式:粗体、斜体等
  • URLSpan 文本超链接

此外还有删除线,缩放大小等不同样式的拓展

用法

1
2
3
SpannableString spannableString = new SpannableString(source);
ImageSpan span = new ImageSpan(context, bitmap);
spannableString.setSpan(span, start, start + emojiStr.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );

解释下用法 首先将原String文字包装成拓展性文字SpannableString 再创建一个需要类型的Span对象,比如表情图片就是ImageSpan, 然后利用SpannableString的setSpan方法,将span对象设置在对应位置, start就是需要附着的内容的开始位置 end是需要附着的内容的开始位置 flag标志位,这里是最常用的EXCLUSIVE_EXCLUSIVE的表示span拓展文本不包含前后, 此外还有INCLUSIVE是包含的意思

举例说明下,原文字是”文章作者是大帅比” 那我创建个粗体拓展内容,并且要将其附着到作者二字上让其变成粗体, 那开始位置就是2,即第三个字;结束位置就是4,即第五个 然后按照”包头不包尾”原则,包含第三个字不包含第五个字,即处理的就是第三四俩字”作者” flag我们将其设为EXCLUSIVE_INCLUSIVE即不包含前包含后

那最终显示的文字就是 “文章作者是大帅比” 如果我们在附着内容及作者二字前面输入内容时,由于是EXCLUSIVE不包含,所以不会跟着变化 “文章boredream作者是大帅比” 如果我们在附着内容及作者二字后面输入内容时,由于是INCLUSIVE包含,新内容也会包含进效果 “文章作者boredream是大帅比”

特殊文字的匹配

知道要处理哪些特殊文本,怎么处理,还有一步是怎么把特殊文本从一段文字里挑出来 即获取到特殊本文的开始和结束位置,以及他的文字内容 这里就要用到正则去进行匹配获取了,正则就不展开介绍了


表情图片显示

文字附着表情图片很简单,用SpannableString中的ImageSpan, 难点在于正则部分的获取处理

首先是正则的写法,根据需要自行编写规则 这里以微博表情为例 String regex = “\[[\u4e00-\u9fa5\w]+\]”; 简单介绍下 最外面是方括号符号,要注意由于[]在正则中有特殊意义, 所以需要用斜杠\转义一下(\本身也需要转义所以是俩\) 中间\u4e00-\u9fa5表示中文, \w表示下划线的任意单词字符

  • 代表一个或者多个 那么这段正则就代表,匹配方括号内有一或多个文字和单词字符的文本

然后去while循环匹配就可以了 用matcher.find获取到匹配的开始位置,作为setSpan的start值, 用matcher.group方法获取到匹配规则的具体表情文字, end值则直接利用开始位置加上表情文字的长度即可

贴上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static SpannableString getEmotionContent(final Context context, final TextView tv, String source) {
     SpannableString spannableString = new SpannableString(source);
     Resources res = context.getResources();

     String regexEmotion = "\\[([\u4e00-\u9fa5\\w])+\\]";
     Pattern patternEmotion = Pattern. compile(regexEmotion);
     Matcher matcherEmotion = patternEmotion.matcher(spannableString);

     while (matcherEmotion.find()) {
            // 获取匹配到的具体字符
           String key = matcherEmotion.group();
            // 匹配字符串的开始位置
            int start = matcherEmotion.start();
            // 利用表情名字获取到对应的图片
           Integer imgRes = EmotionUtils. getImgByName(key);
            if (imgRes != null) {
                 // 压缩表情图片
                 int size = ( int) tv.getTextSize();
                Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes);
                Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);

                ImageSpan span = new ImageSpan(context, scaleBitmap);
                spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
           }
     }
     return spannableString;
}

基本思路前面提过了,图片处理的地方还要再说明下 表情如果直接获取资源文件里的图片,可能会过大,所以方法参数里传入了需要显示的文字控件, 然后利用文字大小去将表情图片再压缩一下

其中 EmotionUtils类就不介绍了,里面就是一个map集合,key为表情名字,value为表情图片的resId, 然后提供一个getImgByName的方法可以根据名字获取图片 键值对需要一个个添加比较麻烦,我们会在最后介绍一个表情添加的脚本,方便快速导入图片资源


表情面板

结构就是ViewPager提供多页滑动,每页都是一个GridView显示20个表情,末尾还有一个删除按钮 ViewPager和GridView的基本使用方法就不介绍了,这里说明下其中的特殊处理

  1. 每个GridView的大小设置 GridView会显示3行7列,共21个Item 为了让Item能大小合适,最好的方法是利用动态计算的方式设置宽高,因为屏幕宽度各有不同 其他的一些大小计算也要注意用dp值转换下获取对应的px再使用,保证适配问题

思路是 获取屏幕宽度,减去间隙距离,然后除以7算出每个Item需要的宽度, 然后再乘以3加上需要的间隙,算出表情面板的高度,利用算出来的值去创建设置面板大小

  1. 需要多少个GridView 一共有多少页呢,虽然表情大部分情况下总数是固定的, 但是最好还是根据代码的方式算出来需要多少页,而非直接人去目测计算要多少个GridView

思路是 for循环全部的图片名字,这里利用map.keySet获取全部的键集合 每次循环都把图片名添加到一个集合中, 然后进行判断,如果满20个了就作为一组表情,新建一个GridView设置上去 最后把所有表情生成的一个个GridView放到一个总view集合中,利用ViewPager显示

  1. GridView末尾删除键处理 适配器和点击事件中,都利用position判断,如果是最后一个就进行特殊的显示和点击处理

这几部分的代码比较乱,但是没有什么新知识点,都是一些逻辑方面的内容,就不贴出来了 最后附件里会放出完整的demo代码,可以下载查看


表情的输入框插入和删除

实质上还是对表情对应的文本数据进行操作,这里最要注意的地方是输入框中的光标问题 输入框其实也是TextView的一种,显示和之前介绍的方法一样, 但是动态的添加和删除就需要注意处理位置的问题了

  1. 手动设置光标 新添加一个表情,应该是在输入框中当前光标的位置插入图片,所以首先要知道光标的位置 获取这里可以用 et.setSelectionStart方法 还有一点,添加完表情以后,光标应该更新到新添加内容后面, 而设置光标位置就要用 et.setSelection(position)方法

  2. 调用系统按钮事件自动处理 删除就比较简单了,这里直接调用系统的 Delete 按钮事件即可, 让某个控件调用按钮事件的方法为 et.displatchKeyEvent(new KeyEvent(action, code)); 其中action就是动作,用ACTION_DOWN按下动作就可以了 而code为按钮事件码,删除对应的就是KEYCODE_DEL


表情添加脚本

以上,知识点就全部介绍完毕了,最后是福利时间 表情少则几十,多则甚至上百~ 一个一个的根据名字设置对应表情键值对会各种痛苦 这里推荐编写脚本进行处理,也就是写段功能自动的生成所需代码

这段就比较灵活了,需要根据不同需要编写脚本,所以主要是提供个思路然后以微博为例编写代码

微博中表情是”[表情文字]”,而表情图片名字则是”d_表情拼音” (此外还有其他的名字样式,比如h_或者emoji_等等,暂时不管,处理原理都差不多)

这里要先说明一下前提,必须要按照一定规则来才能进行这种半自动化的处理, 如果表情干脆名字就是瞎起的,那就没辙了, 如果是公司自己做应用的话,一定要让美工切图后导出的图片文件名按照套路出牌

新浪微博中d_表情拼音这里就都是中文对应的拼音而非英文,且中文都是和[表情文字]一致的, 比如害羞表情的文字就是[害羞],而图片名字就是d_haixiu

那微博这里处理就可以按照套路来了

  1. 打开微博原版客户端,把所有表情全部选中,然后发出来
  2. 在日志里获取到这段图片对应的文字数据
  3. 用表情文字规则对这段文字进行循环匹配
  4. 每次循环的时候都,把匹配的表情名字转为拼音(pinyin4j等工具)
  5. 把表情文字转成的拼音再拼成图片资源的名字(微博这里就是加个”d_“前缀)
  6. 拼接map.put的代码
  7. 循环完成后,打印出来全部的map.put代码,然后复制到我们的表情工具类中使用

贴上代码,脚本直接新建一个java的项目放在main函数里运行就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void weiboEmoji() {
     StringBuilder sb = new StringBuilder();

     String names = "[羞羞哒甜馨][萌神奥莉][带着微博去旅行][爱红包][拍照][马到成功]→_→[呵呵][嘻嘻][哈哈][爱你][挖鼻屎][吃惊][晕][泪][馋嘴][抓狂][哼][可爱][怒][汗][害羞][睡觉][钱][偷笑][笑cry][doge][喵喵][酷][衰][闭嘴][鄙视][花心][鼓掌][悲伤][思考][生病][亲亲][怒骂][太开心]" +
                 "[懒得理你][右哼哼][左哼哼][嘘][委屈][吐][可怜][打哈气][挤眼][失望][顶][疑问][困][感冒][拜拜][黑线][阴险][打脸][傻眼][互粉][心][伤心][猪头][熊猫][兔子]" ;

     String regexEmoji = "\\[([\u4e00-\u9fa5a-zA-Z0-9])+\\]";
     Pattern patternEmoji = Pattern. compile(regexEmoji);
     Matcher matcherEmoji = patternEmoji.matcher(names);

     CharacterParser parser = CharacterParser. getInstance();
     while (matcherEmoji.find()) { // 如果可以匹配到
           String key = matcherEmoji.group(); // 获取匹配到的具体字符

           String pinyinName = "d_" + parser.getSpelling(key).replace("[" , "" ).replace("]" , "" );
           sb.append( "emojiMap.put(\"" + key + "\", R.drawable." + pinyinName + ");\n");
     }
     System.out.println(sb.toString());
}

运行结果,从控制台复制代码粘贴到项目里的工具类中即可 emoji2

最后,还有要注意的地方 可能有的表情文件名非不按套路来, 比如笑哭的表情,文字就是[笑cry],而图片文件名是d_xiaoku 那么也没关系,你复制到项目中,如果图片资源匹配不上的话也会报错提示,进行对应修改即可

done