现在几乎所有的聊天类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.youkuaiyun.com/detail/u011002668/9462085
本期节目就到这里,感谢大家的收看,下期再见。
(PS:最初写博客就是为了记录自己学习的知识点,但既然写了,还是想写好点,或许对别人能起到帮助的作用,本人水平有限,如果有不对的地方,欢迎指正。)