Android表情文字EmotionText解析

現在幾乎所有的聊天類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:最初寫博客就是爲了記錄自己學習的知識點,但既然寫了,還是想寫好點,或許對別人能起到幫助的作用,本人水平有限,如果有不對的地方,歡迎指正。)

發佈了40 篇原創文章 · 獲贊 88 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章