Android 表情功能

思路:
表情图片资源
表情展示面板
输入删除逻辑


首先需要下载表情相关资源,链接:http://pan.baidu.com/s/1pLLTEkV 密码:etow,下载完后将图片添加到项目中。
然后自定义一个工具类,作用一:使得表情图片的文件名和在项目中的资源id作为map集合的键值对一一对应。

public static final int[] EmojiResArray = {
            R.drawable.d_aini,
            R.drawable.d_aoteman,
            R.drawable.d_baibai,
            ...
            R.drawable.w_yueliang,
    };

  public static final String[] EmojiTextArray = {
            "[爱你]",
            "[奥特曼]",
            "[拜拜]",
            ...
            "[月亮]",
    };

    private static final Map<String, Integer> EmojiMap = new HashMap<>();

    static {
        for (int i = 0; i < EmojiResArray.length; i++) {
            EmojiMap.put(EmojiTextArray[i], EmojiResArray[i]);
        }
    }

   public static int getImgByName(String key) {
        return EmojiMap.get(key);
    }

作用二:用于将服务器返回的类似[爱你][奥特曼]格式的字符串转换成对应表情图片的复合文本。

  //正则表达式匹配,[爱你][奥特曼]——> 表情的复合文本
    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 = EmojiUtils.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;
    }

表情面板布局:一个Fragment上填充一个ViewPager容器,ViewPager每一页都是一个3*7的GridView布局,其中每一个都是一张表情图片,并为其添加点击事件。
EViewPagerAdapter和EGridViewAdapter的具体实现代码不再赘述,需要注意的是他们的数据源分别是以GridView为泛型的和Integer为泛型的list集合。
EmojiFragment中需要自定义一个内部类接口,通过接口回调返回点击的表情的顺序序号,从而获取对应的文字名

    private void init() {

        List<GridView> gridViewList = new ArrayList<>();
        EmojiGridViewAdapter gridViewAdapter = null;
        for (int i = 0; i < EmojiUtils.EmojiResArray.length; i++) {
            if (i % 20 == 0) {
                final int po = i;
                GridView view = new GridView(getContext());
                view.setNumColumns(7);
                view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                    @Override
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                        if (po >= 20) {
                            mOnClickOnFragment.onClickFragment(position + po);
                        } else {
                            mOnClickOnFragment.onClickFragment(position);
                        }
                    }
                });
                gridViewAdapter = new EmojiGridViewAdapter();
                gridViewAdapter.addInt(getContext(), EmojiUtils.EmojiResArray[i]);
                view.setAdapter(gridViewAdapter);
                gridViewList.add(view);
                continue;
            }
            gridViewAdapter.addInt(getContext(), EmojiUtils.EmojiResArray[i]);
        }

        EmojiViewPagerAdapter adapter = new EmojiViewPagerAdapter();
        adapter.setGridViews(gridViewList);
        mEmojiViewPager.setAdapter(adapter);
    }


    public interface OnClickOnFragment {
        void onClickFragment(int i);
    }

使用时只需将该EmojiFragment表情面板填充到布局中即可,

 //填充表情到布局中
        EmojiFragment emojiFragment = new EmojiFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.emojiLayout, emojiFragment).commit();

输入删除逻辑思路:当我们点击表情面板来输入表情时,根据接口回调的int值得到相应的表情图片id,然后以复合文本的形式在EditText中显示,同时会对表情图片进行压缩使其大小和输入的文字一般大,


    //根据数组中position获得相对应的表情的复合文本
    public static SpannableString getEmotionEditText(Context context, EditText editText, int position) {
        SpannableString spanString = new SpannableString(" ");
        int size = (int) editText.getTextSize();
        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), EmojiUtils.EmojiResArray[position]);
        Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
        ImageSpan span = new ImageSpan(context, scaleBitmap);
        spanString.setSpan(span, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return spanString;
    }

但是不会将该复合文本提交服务器,而是将表情图片对应的文件名以"[爱你]"的格式拼接到mCommitString字符串中,然后才提交服务器。
需要注意的是:一个表情图片的复合文本添加到EditText中只占一个字符,但是需要拼接的[爱你]确实四个字符串,这样是如何在EditText中任意输入表情时,mCommitString字符串是如何在准确位置拼接的?

定义一个泛型是Integer的list集合,该集合的索引值看做是mEdiText中的字符的排列位置,同时也是mCommitString字符串中字符的排列位置,该集合的value值则是该位置在mCommitString字符串中的长度,比如:"我[爱你]"在EditText中第2个位置是一个表情的复合文本,只占1个字符,但是在mCommitString字符串中第2个位置确实4个字符,此时该集合索引为1时,value值为1,索引为2时,value值为4;

//表情的点击监听事件
        emojiFragment.setOnClickOnFragment(new EmojiFragment.OnClickOnFragment() {
            @Override
            public void onClickFragment(int i) {
//鼠标光标选中的位置
                int selectionStart = mEditText.getSelectionStart();
//EditText中添加复合文本
                SpannableString emotionEditText = EmojiUtils.getEmotionEditText(SendCardDetailsActivity.this, mEditText, i);
                mEditText.getText().insert(selectionStart, emotionEditText);
                mList.add(selectionStart, EmojiUtils.EmojiTextArray[i].length());
                int x = 0;
                for (int i1 = 0; i1 < selectionStart; i1++) {
                    if (mList.get(i1) != null) {
                        x += mList.get(i1);
                    }
                }
                LogUtils.d(TAG, "x:" + x);
                mCommitString.insert(x, EmojiUtils.EmojiTextArray[i].trim());
            }

当在EditText的任意位置插入的不是表情而是空格,汉字,字母时也面临这一个问题,就是在EditText的某一位置插入一个字符时,同时需要在mCommitString字符串准确插入该字符,由于在某一位置是一对多的关系,就很有可能造成提交的字符串位置错乱,比如向"我[爱你]的后面插入"呀"字,就有可能出现"我[呀爱你]"这种现象;
解决办法就是:在EditText中插入字符时,得到插入的初始位置,然后遍历累加小于该初始值所有索引的value,得到的就是mCommitString字符串中将要插入的准确初始位置,删除也是同样原理,初始位置和结束位置之间索引对应的value值累加就是将要删除的真正长度,

 //内容EditText 文本改变监听
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        LogUtils.d(TAG, start + ":" + before + ";" + count + "");
        int y1 = 0;
        int y2 = 0;
        int x1 = 0;
        int x2 = start;
//count ==0 时是删除操作
        if (count == 0) {
            for (int i = 0; i <= start; i++) {
                if (mList.get(i) != null) {
                    y1 += mList.get(i);
                }
            }
            for (int i = 0; i <= start - before; i++) {
                if (mList.get(i) != null) {
                    y2 += mList.get(i);
                }
            }
            LogUtils.d(TAG, "y1:" + y1);
            LogUtils.d(TAG, "y2:" + y2);
            mCommitString.delete(y2, y1);
        } else {
//如果是一次输入一大段汉字,此时就不是一对多的关系,EditText和`mCommitString`字符串就是一对一的关系,在集合中插入大段字符串的长度的索引区间的value值都赋值为1,就保证集合长度和字符串长度一致
            for (int i = 0; i < count; i++) {
                mList.add(x2++, 1);
            }
            for (int i = 0; i < start; i++) {
                if (mList.get(i) != null) {
                    x1 += mList.get(i);
                }
            }
            LogUtils.d(TAG, "x1:" + x1);
//当是输入表情时,忽略空格;否则允许输入空格
            if (mIsEmoji) {
                mCommitString.insert(x1, s.toString().substring(start, start + count).trim());
            } else
                mCommitString.insert(x1, s.toString().substring(start, start + count));
            mIsEmoji = false;

        }
        LogUtils.d(TAG, mCommitString.toString());
    }

    @Override
    public void afterTextChanged(Editable s) {

    }

笔者水平有限,希望大家教我

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 目标·生活
    覃小初阅读 139评论 0 0
  • 我想做一朵云 一朵你知道的云 风起时,看你的样子 雨落时,做你的伞 我想做一朵云 一朵知道你的云 你饿时,做你的棉...
    四斤阅读 967评论 1 6
  • 目录 连续好几天,我都被逼充当考霸答案搬运工,成功让丫头在大学的最后一年也当了一回考霸。 好不容易终于考完试了,我...
    lekli阅读 601评论 0 6