1. 程式人生 > >Android 表情功能的完整處理方案

Android 表情功能的完整處理方案

概述

1.原理和實現思路
2.表情圖片顯示3.表情面板
4.表情的輸入框插入和刪除
5.表情新增指令碼

Android中表情功能,一般都不是用ImageView去設定圖片實現的,
表情一般會巢狀在文字之中,那麼如何實現呢,這裡就介紹一下其中的原理,此外還有相關功能的實現思路和具體程式碼

先看下良心動態圖~


------------------------------------------------------------------------------

1.原理和思路

a.表情內容的資料格式
表情看上去是圖片,但是在資料傳輸的時候本質上是一個特殊文字
比如QQ表情就是一個 "/表情字母"的結構,比如害羞的表情就是/hx,呲牙就是/cy...

微博裡表情就是"[表情名字]"的介面,比如可愛的表情就是[可愛]等等...

b.特殊文字顯示圖片的原理
需要用到安卓中的SpannableString拓展性字串相關知識
SpannableString可以讓一段字串在顯示的時候,將其中某小段文字附著上其他內容,
附著的拓展內容可能是圖片,或者是文字格式,比如加粗斜體等

下面稍微展開介紹下SpannableString,會的可以跳過

常用的拓展內容包含有
BackgroundColorSpan 背景色 
ClickableSpan 文字可點選,有點選事件
UnderlineSpan 下劃線
ImageSpan 圖片
StyleSpan 字型樣式:粗體、斜體等
URLSpan 文字超連結

此外還有刪除線,縮放大小等不同樣式的拓展

用法
  1. SpannableString spannableString = new SpannableString(source);
  2. ImageSpan span = new ImageSpan(context, bitmap);
  3. 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是大帥比"


c.特殊文字的匹配
知道要處理哪些特殊文字,怎麼處理,還有一步是怎麼把特殊文字從一段文字裡挑出來
即獲取到特殊本文的開始和結束位置,以及他的文字內容
這裡就要用到正則去進行匹配獲取了
正則的規則就不細說了,下面介紹下表情這裡的正則匹配

------------------------------------------------------------------------------

2.表情圖片顯示

文字附著表情圖片很簡單,用SpannableString中的ImageSpan,
難點在於正則部分的獲取處理

首先是正則的寫法,根據需要自行編寫規則
這裡以微博表情為例
String regex = "\\[[\u4e00-\u9fa5\\w]+\\]";
簡單介紹下

最外面是方括號符號,要注意由於[]在正則中有特殊意義,
所以需要用斜槓\轉義一下(\本身也需要轉義所以是倆\\)

中間\u4e00-\u9fa5表示中文,
\\w表示下劃線的任意單詞字元
+ 代表一個或者多個
那麼這段正則就代表,匹配方括號內有一或多個文字和單詞字元的文字


然後去while迴圈匹配就可以了
用matcher.find獲取到匹配的開始位置,作為setSpan的start值,
用matcher.group方法獲取到匹配規則的具體表情文字,
end值則直接利用開始位置加上表情文字的長度即可

貼上程式碼
  1. public static SpannableString getEmotionContent( final Context context, final TextView tv, String source) {
  2.      SpannableString spannableString = new SpannableString(source);
  3.      Resources res = context.getResources();
  4.      String regexEmotion = "\\[([\u4e00-\u9fa5\\w])+\\]" ;
  5.      Pattern patternEmotion = Pattern. compile(regexEmotion);
  6.      Matcher matcherEmotion = patternEmotion.matcher(spannableString);
  7.       while (matcherEmotion.find()) {
  8.             // 獲取匹配到的具體字元
  9.            String key = matcherEmotion.group();
  10.             // 匹配字串的開始位置
  11.             int start = matcherEmotion.start();
  12.             // 利用表情名字獲取到對應的圖片
  13.            Integer imgRes = EmotionUtils. getImgByName(key);
  14.             if (imgRes != null) {
  15.                  // 壓縮表情圖片
  16.                  int size = ( int) tv.getTextSize();
  17.                 Bitmap bitmap = BitmapFactory.decodeResource(res, imgRes);
  18.                 Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
  19.                 ImageSpan span = new ImageSpan(context, scaleBitmap);
  20.                 spannableString.setSpan(span, start, start + key.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
  21.            }
  22.      }
  23.       return spannableString;
  24. }
複製程式碼


基本思路前面提過了,圖片處理的地方還要再說明下
表情如果直接獲取資原始檔裡的圖片,可能會過大,所以方法引數裡傳入了需要顯示的文字控制元件,
然後利用文字大小去將表情圖片再壓縮一下

其中
EmotionUtils類就不介紹了,裡面就是一個map集合,key為表情名字,value為表情圖片的resId,
然後提供一個getImgByName的方法可以根據名字獲取圖片
鍵值對需要一個個新增比較麻煩,我們會在最後介紹一個表情新增的指令碼,方便快速匯入圖片資源

------------------------------------------------------------------------------

3.表情面板


結構就是ViewPager提供多頁滑動,每頁都是一個GridView顯示20個表情,末尾還有一個刪除按鈕
ViewPager和GridView的基本使用方法就不介紹了,這裡說明下其中的特殊處理

a.每個GridView的大小設定
GridView會顯示3行7列,共21個Item
為了讓Item能大小合適,最好的方法是利用動態計算的方式設定寬高,因為螢幕寬度各有不同
其他的一些大小計算也要注意用dp值轉換下獲取對應的px再使用,保證適配問題

思路是
獲取螢幕寬度,減去間隙距離,然後除以7算出每個Item需要的寬度,
然後再乘以3加上需要的間隙,算出表情面板的高度,利用算出來的值去建立設定面板大小

b.需要多少個GridView
一共有多少頁呢,雖然表情大部分情況下總數是固定的,
但是最好還是根據程式碼的方式算出來需要多少頁,而非直接人去目測計算要多少個GridView

思路是
for迴圈全部的圖片名字,這裡利用map.keySet獲取全部的鍵集合
每次迴圈都把圖片名新增到一個集合中,
然後進行判斷,如果滿20個了就作為一組表情,新建一個GridView設定上去
最後把所有表情生成的一個個GridView放到一個總view集合中,利用ViewPager顯示

c.GridView末尾刪除鍵處理
介面卡和點選事件中,都利用position判斷,如果是最後一個就進行特殊的顯示和點選處理

這幾部分的程式碼比較亂,但是沒有什麼新知識點,都是一些邏輯方面的內容,就不貼出來了
最後附件裡會放出完整的demo程式碼,可以下載檢視

------------------------------------------------------------------------------


4.表情的輸入框插入和刪除

實質上還是對錶情對應的文字資料進行操作,這裡最要注意的地方是輸入框中的游標問題
輸入框其實也是TextView的一種,顯示和之前介紹的方法一樣,
但是動態的新增和刪除就需要注意處理位置的問題了

a.手動設定游標
新新增一個表情,應該是在輸入框中當前游標的位置插入圖片,所以首先要知道游標的位置
獲取這裡可以用 et.setSelectionStart方法
還有一點,新增完表情以後,游標應該更新到新新增內容後面,
而設定游標位置就要用 et.setSelection(position)方法




b.呼叫系統按鈕事件自動處理
刪除就比較簡單了,這裡直接呼叫系統的 Delete 按鈕事件即可,
讓某個控制元件呼叫按鈕事件的方法為
et.displatchKeyEvent(new KeyEvent(action, code));
其中action就是動作,用ACTION_DOWN按下動作就可以了
而code為按鈕事件碼,刪除對應的就是KEYCODE_DEL

------------------------------------------------------------------------------

5.表情新增指令碼

以上,知識點就全部介紹完畢了,最後是福利時間
表情少則幾十,多則甚至上百~ 一個一個的根據名字設定對應表情鍵值對會各種痛苦
這裡推薦編寫指令碼進行處理,也就是寫段功能自動的生成所需程式碼

這段就比較靈活了,需要根據不同需要編寫指令碼,所以主要是提供個思路然後以微博為例編寫程式碼

微博中表情是"[表情文字]",而表情圖片名字則是"d_表情拼音"
(此外還有其他的名字樣式,比如h_或者emoji_等等,暫時不管,處理原理都差不多)

這裡要先說明一下前提,必須要按照一定規則來才能進行這種半自動化的處理,
如果表情乾脆名字就是瞎起的,那就沒轍了,
如果是公司自己做應用的話,一定要讓美工切圖後匯出的圖片檔名按照套路出牌

新浪微博中d_表情拼音這裡就都是中文對應的拼音而非英文,且中文都是和[表情文字]一致的,
比如害羞表情的文字就是[害羞],而圖片名字就是d_haixiu


那微博這裡處理就可以按照套路來了
1) 開啟微博原版客戶端,把所有表情全部選中,然後發出來
2) 在日誌裡獲取到這段圖片對應的文字資料
3) 用表情文字規則對這段文字進行迴圈匹配
4) 每次迴圈的時候都,把匹配的表情名字轉為拼音(pinyin4j等工具)
5) 把表情文字轉成的拼音再拼成圖片資源的名字(微博這裡就是加個"d_"字首)
6) 拼接map.put的程式碼
7) 迴圈完成後,打印出來全部的map.put程式碼,然後複製到我們的表情工具類中使用

貼上程式碼,指令碼直接新建一個java的專案放在main函式裡執行就可以了
  1. public static void weiboEmoji() {
  2.      StringBuilder sb = new StringBuilder();
  3.      String names = "[羞羞噠甜馨][萌神奧莉][帶著微博去旅行][愛紅包][拍照][馬到成功]→_→[呵呵][嘻嘻][哈哈][愛你][挖鼻屎][吃驚][暈][淚][饞嘴][抓狂][哼][可愛][怒][汗][害羞][睡覺][錢][偷笑][笑cry][doge][喵喵][酷][衰][閉嘴][鄙視][花心][鼓掌][悲傷][思考][生病][親親][怒罵][太開心]" +
  4.                  "[懶得理你][右哼哼][左哼哼][噓][委屈][吐][可憐][打哈氣][擠眼][失望][頂][疑問][困][感冒][拜拜][黑線][陰險][打臉][傻眼][互粉][心][傷心][豬頭][熊貓][兔子]" ;
  5.      String regexEmoji = "\\[([\u4e00-\u9fa5a-zA-Z0-9])+\\]" ;
  6.      Pattern patternEmoji = Pattern. compile(regexEmoji);
  7.      Matcher matcherEmoji = patternEmoji.matcher(names);
  8.      CharacterParser parser = CharacterParser. getInstance();
  9.       while (matcherEmoji.find()) { // 如果可以匹配到
  10.            String key = matcherEmoji.group(); // 獲取匹配到的具體字元
  11.            String pinyinName = "d_" + parser.getSpelling(key).replace("[" , "" ).replace("]" , "" );
  12.            sb.append( "emojiMap.put(\"" + key + "\", R.drawable." + pinyinName + ");\n" );
  13.      }
  14.      System. out.println(sb.toString());
  15. }
複製程式碼



執行結果,從控制檯複製程式碼貼上到專案裡的工具類中即可



可能有的表情檔名就是不按套路來,
比如新浪微博這裡的笑哭的表情,文字就是[笑cry],而圖片檔名是d_xiaoku
那麼也沒關係,你複製到專案中,如果圖片資源匹配不上的話也會報錯提示,進行對應修改即可

------------------------------------------------------------------------------