現在幾乎所有的聊天類App都支持輸入表情混合文字,但是Android的EditText通常只能輸入文字。
今天就來實現一個可以顯示錶情混合文字的EditText和TextView。
老規矩,在節目開始之前,先來一個搞笑段子:
“大師,我有目標,有遠見,心胸開闊,但爲什麼生活總不如意?”
大師把我帶到一棵梅花樹前。看着那暗暗吐露芬芳的梅花,我大有所悟。
“大師,你是要告訴我梅花香自苦寒來,只要堅持就能成功?”
“傻逼,梅前你說個屁啊!”
先來看一下實現的效果圖
整體功能就兩個:可以在EditText中輸入表情和文字,接收到表情和文字混合的消息可以顯示在TextView中。
先不看代碼,先說一下原理,把原理弄明白了,再看代碼就很清晰。
在這個工程中,使用的就是系統的EditText和TextView,所以可以看出來顯示錶情並不是我自定義的功能,而是本身就支持的。只不過通常EditText和TextView在顯示文本時,我們輸入的都是普通的String或者Charsequence,要顯示錶情,就不能輸入普通的String了,而是輸入一個特殊的東西,叫SpannableString。
SpannableString,從名字也能看出來,它就是String字符串,只不過它在顯示的時候,會把字符串中的一部分替換成一張圖片,而當你調用editText.getText()方法時,返回的還是String字符串。所以它就是一段字符串和一張圖片的對應。在顯示的時候是圖片,getText()取出時是String。
這個原理解釋清楚後,示例圖所展示的功能就不難了。
首先是輸入:
每點擊一個表情,就構建一個SpannableString對象,這個SpannableString對象就是一個String和一張表情圖片的對應,然後設置給EditText,EditText顯示成表情圖片;點擊發送,取出EditText的內容,返回的就是String,然後把String發送出去,對方接受到String後,找到SpannableString對象中和表情圖片對應的String,重新構建SpannableString對象,再顯示在TextView中。
繞來繞去,就是String和表情圖片的轉來轉去。
下面來講代碼實現:
1)、找24張表情圖片,我就是從QQ中拿來的。
2)、在assets中新建emotions.xml文件
<emotions>
<emotion>
<code><![CDATA[[em:1:]]]></code>
<name>f001</name>
</emotion>
<emotion>
<code><![CDATA[[em:2:]]]></code>
<name>f002</name>
</emotion>
<emotion>
<code><![CDATA[[em:3:]]]></code>
<name>f003</name>
</emotion>
<emotion>
<code><![CDATA[[em:4:]]]></code>
<name>f004</name>
</emotion>
<emotion>
<code><![CDATA[[em:5:]]]></code>
<name>f005</name>
</emotion>
<emotion>
<code><![CDATA[[em:6:]]]></code>
<name>f006</name>
</emotion>
<emotion>
<code><![CDATA[[em:7:]]]></code>
<name>f007</name>
</emotion>
<emotion>
<code><![CDATA[[em:8:]]]></code>
<name>f008</name>
</emotion>
<emotion>
<code><![CDATA[[em:9:]]]></code>
<name>f009</name>
</emotion>
<emotion>
<code><![CDATA[[em:10:]]]></code>
<name>f010</name>
</emotion>
<emotion>
<code><![CDATA[[em:11:]]]></code>
<name>f011</name>
</emotion>
<emotion>
<code><![CDATA[[em:12:]]]></code>
<name>f012</name>
</emotion>
<emotion>
<code><![CDATA[[em:13:]]]></code>
<name>f013</name>
</emotion>
<emotion>
<code><![CDATA[[em:14:]]]></code>
<name>f014</name>
</emotion>
<emotion>
<code><![CDATA[[em:15:]]]></code>
<name>f015</name>
</emotion>
<emotion>
<code><![CDATA[[em:16:]]]></code>
<name>f016</name>
</emotion>
<emotion>
<code><![CDATA[[em:17:]]]></code>
<name>f017</name>
</emotion>
<emotion>
<code><![CDATA[[em:18:]]]></code>
<name>f018</name>
</emotion>
<emotion>
<code><![CDATA[[em:19:]]]></code>
<name>f019</name>
</emotion>
<emotion>
<code><![CDATA[[em:20:]]]></code>
<name>f020</name>
</emotion>
<emotion>
<code><![CDATA[[em:21:]]]></code>
<name>f021</name>
</emotion>
<emotion>
<code><![CDATA[[em:22:]]]></code>
<name>f022</name>
</emotion>
<emotion>
<code><![CDATA[[em:23:]]]></code>
<name>f023</name>
</emotion>
<emotion>
<code><![CDATA[[em:24:]]]></code>
<name>f024</name>
</emotion>
</emotions>
這裏就是24個表情對應的String,這個保存在assets中,相當於一個配置文件。最紅要用Java對象來保存這些。
3)、新建Java Bean:Emotion
public class Emotion {
private String code = null;
private String name = null;
public Emotion() {}
public Emotion(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4)、解析emotions.xml文件,保存成List
public static List<Emotion> getEmotions(InputStream inputStream) {
XmlPullParser parser = Xml.newPullParser();
int eventType = 0;
List<Emotion> emotions = null;
Emotion emotion = null;
try {
parser.setInput(inputStream, "UTF-8");
eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
emotions = new ArrayList<Emotion>();
break;
case XmlPullParser.START_TAG:
if ("emotion".equals(parser.getName())) {
emotion = new Emotion();
} else if ("code".equals(parser.getName())) {
emotion.setCode(parser.nextText());
} else if ("name".equals(parser.getName())) {
emotion.setName(parser.nextText());
}
break;
case XmlPullParser.END_TAG:
if ("emotion".equals(parser.getName())) {
emotions.add(emotion);
emotion = null;
}
break;
default:
break;
}
eventType = parser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
return emotions;
}
這個就是常規的xml解析,無所謂用哪種解析方法,反正返回正確就行。
5)、用GridView展示所有的表情
ArrayList<HashMap<String, Object>> items = getItems();
SimpleAdapter saImageItems = new SimpleAdapter(this, items,
R.layout.emotion_item, new String[] { "itemImage" },
new int[] { R.id.iv_emotion });
gvEmotions.setAdapter(saImageItems);
6)、點擊表情時,構建SpannableString顯示在EditText中(重點)
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Emotion emotion = emotions.get(position);
int cursor = etInput.getSelectionStart();
Field f;
try {
f = (Field) R.drawable.class.getDeclaredField(emotion.getName());
int j = f.getInt(R.drawable.class);
Drawable d = getResources().getDrawable(j);
int textSize = (int)etInput.getTextSize();
d.setBounds(0, 0, textSize, textSize);
String str = null;
int pos = position + 1;
if (pos < 10) {
str = "f00" + pos;
} else if (pos < 100) {
str = "f0" + pos;
} else {
str = "f" + pos;
}
SpannableString ss = new SpannableString(str);
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM);
ss.setSpan(span, 0, str.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
etInput.getText().insert(cursor, ss);
} catch (Exception e) {
e.printStackTrace();
}
}
這步是關鍵,首先根據點擊的位置獲取Emotion,然後根據name,用反射獲取Drawable。
然後用Drawable創建ImageSpan,把ImageSpan設置給SpannableString。
最後調用editText.getText().insert(cursor, ss);設置顯示內容。
詳細解釋:
24個表情是用f001-f024表示的,對應的24個圖片名字也是f001.png-f024.png
根據我點擊的位置,構建出具體的str和drawable。
str用來創建SpannableString。
SpannableString ss = new SpannableString(str);
drawable用來創建ImageSpan。
ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM);
那這兩個是怎麼關聯的呢?就是下一行代碼:
ss.setSpan(span, 0, str.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
用span來對應ss中從0到str.length()這段字符串。
最後調用顯示
editText.getText().insert(cursor, ss);
整個過程就是這樣。
7)接受到表情文字混合的消息,顯示在TextView中
String receiveString = receiveMessage();
SpannableString spannableString = ExpressionUtil.getExpressionString(receiveString );
textView.setText(spannableString );
就三部,關鍵是第二部getExpressionString方法是怎麼實現的。
public static final String PATTEN_STR = "f0[0-9]{2}|f10[0-7]";
public static SpannableString getExpressionString(Context context, String str, int textSize) {
SpannableString spannableString = new SpannableString(str);
Pattern sinaPatten = Pattern.compile(PATTEN_STR, Pattern.CASE_INSENSITIVE);
try {
dealExpression(context, spannableString, textSize, sinaPatten, 0);
} catch (Exception e) {
Log.e("dealExpression", e.getMessage());
}
return spannableString;
}
public static void dealExpression(Context context, SpannableString spannableString, int textSize, Pattern patten, int start) throws Exception {
Matcher matcher = patten.matcher(spannableString);
while (matcher.find()) {
String key = matcher.group();
if (matcher.start() < start) {
continue;
}
Field field = R.drawable.class.getDeclaredField(key);
int resId = field.getInt(R.drawable.class);
if (resId != 0) {
Drawable d = context.getResources().getDrawable(resId);
d.setBounds(0, 0, textSize, textSize);
ImageSpan imageSpan = new ImageSpan(d);
int end = matcher.start() + key.length();
spannableString.setSpan(imageSpan, matcher.start(), end,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
if (end < spannableString.length()) {
dealExpression(context, spannableString, textSize, patten, end);
}
break;
}
}
}
我們所有的表情都是用f001-f024來對應的,所以,接受到消息String後,就要找出String中所有匹配f001-f024這種格式的,然後替換成對應的表情圖片。
所以這段代碼看似很多,其實就是對接受到的String,用正則表達式進行遍歷匹配,每匹配到一個,就把匹配到的那一段用一個ImageSpan來對應,直到全部匹配完。
最後把最終的SpnanableString設置給TextView。
大工告成!!!
至此,整個實現的邏輯就講完了,但是我的工程中遠不止這些,還有很多邊緣性的功能,但核心的東西都講了。
最後,我把完整的工程代碼放出來,需要的朋友下載吧。
http://download.csdn.net/detail/u011002668/9462085
本期節目就到這裏,感謝大家的收看,下期再見。
(PS:最初寫博客就是爲了記錄自己學習的知識點,但既然寫了,還是想寫好點,或許對別人能起到幫助的作用,本人水平有限,如果有不對的地方,歡迎指正。)