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 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 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) {

    }

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

你可能感兴趣的:(Android 表情功能)