Android一步一步教你实现Emoji表情键盘

背景:

说到聊天,就离不开文字、表情和图片,表情和图片增加了聊天的趣味性,让原本无聊的文字瞬间用表情动了起来,今天给大家带来的是表情键盘,教你一步一步实现,先来看下效果图:

效果图

功能:

1、如何控制表情键盘与输入法的切换

2、如何解析表情

3、如何处理表情与非表情的删除

实现:

明确了各个要解决的问题,下面我们逐个来实现

表情键盘与输入法切换

查了一下相关资料,有如下方案:

方案一:动态改变SoftInputMode

软键盘显示时将SoftInputMode设置为「stateVisible|adjustResize」,表情键盘显示时调整为「adjustPan」

方案二:Dialog

直接在软键盘上显示一个Dialog,可避开大部分切换逻辑,但是在打开当前页面后存在软键盘和Dialog冲突问题

观察QQ、微信、微博、陌陌后发现,他们的表情键盘和软键盘切换,并不会导致聊天内容(ListView、RecyclerView)的跳动,基本就可以推测SoftInputMode就是adjustsPan。

明确了adjustPan那就好办了,既然聊天内容(ListView、RecyclerView)不会跳动,那么在软键盘切换至表情键盘的时候,底部肯定有一个和软键盘高度一致的View,只需在点击表情的时候将软键盘隐藏,显示表情键盘,在点击EditText的时候显示软键盘,隐藏表情键盘。

来梳理一下知识点:

1、如何获取软键盘高度

2、如何手动控制软键盘的显示与隐藏

3、如何避免在别的页面切到当前界面因软键盘的状态变化而冲突

获取软键盘高度
private int getSupportSoftInputHeight() {
        Rect r = new Rect();
        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
        int screenHeight = mActivity.getWindow().getDecorView().getRootView().getHeight();
        int softInputHeight = screenHeight - r.bottom;
        if (Build.VERSION.SDK_INT >= 20) {
            // When SDK Level >= 20 (Android L),
            // the softInputHeight will contain the height of softButtonsBar (if has)
            softInputHeight = softInputHeight - getSoftButtonsBarHeight();
        }
        if (softInputHeight < 0) {
            Log.w("EmotionInputDetector", "Warning: value of softInputHeight is below zero!");
        }
        if (softInputHeight > 0) {
            sp.edit().putInt(SHARE_PREFERENCE_TAG, softInputHeight).apply();
        }
        return softInputHeight;
    }

这里的原理是通过当前Activity获取RootView的高度减去Activity自身的高度,就得到了软键盘的高度,但是发现在有虚拟按键的手机上在没有显示软键盘时减出来的高度总是144,后来查了下资料,发现在API>18时有软键盘的手机需要减去底部虚拟按键的高度。

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private int getSoftButtonsBarHeight() {
        DisplayMetrics metrics = new DisplayMetrics();
        mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
        int usableHeight = metrics.heightPixels;
        mActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
        int realHeight = metrics.heightPixels;
        if (realHeight > usableHeight) {
            return realHeight - usableHeight;
        } else {
            return 0;
        }
    }
把获取到的高度设置给表情键盘
    private void showEmotionLayout() {
        int softInputHeight = getSupportSoftInputHeight();
        if (softInputHeight == 0) {
            softInputHeight = sp.getInt(SHARE_PREFERENCE_TAG, 400);
        }
        hideSoftInput();
        mEmotionLayout.getLayoutParams().height = softInputHeight;
        mEmotionLayout.setVisibility(View.VISIBLE);
    }
控制表情的显示与隐藏
    private void showSoftInput() {
        mEditText.requestFocus();
        mEditText.post(new Runnable() {
            @Override
            public void run() {
                mInputManager.showSoftInput(mEditText, 0);
            }
        });
    }


    private void hideSoftInput() {
        mInputManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
    }

在测试后发现一个问题,点击表情按钮,输入框会抖动,分析下这个过程,点击表情按钮,关闭软键盘,此时Activity的高度发生变化,高度变高,输入框回到底部,再打开表情键盘,此时输入框又被顶上来,输入框看起来上下抖动,经多次测试发现无论是先隐藏软键盘还是先显示表情键盘都存在这个问题,思考过后,既然输入框会上下抖动,那么固定它的位置不就行了,那么问题来了,如何固定它的位置呢?

举个栗子,假如在一个LinearLayout里面有若干个控件,如果里面的控件的位置大小都不变,那么即使在软键盘显示和隐藏(Activity的高度发生变化),也不会隐藏输入框的位置,自然也就不会发生跳动问题。

锁定解锁内容高度(ListView、RecyclerView)

    private void lockContentHeight() {
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentView.getLayoutParams();
        params.height = mContentView.getHeight();
        params.weight = 0.0F;
    }

    private void unlockContentHeightDelayed() {
        mEditText.postDelayed(new Runnable() {
            @Override
            public void run() {
                ((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;
            }
        }, 200L);
    }
表情面板控制
    public EmotionInputDetector bindToEmotionButton(final CheckBox emotionButton) {
        mEmojiView = emotionButton;
        emotionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mEmotionLayout.isShown()) {
                    lockContentHeight();
                    hideEmotionLayout(true);
                    mEmojiView.setChecked(false);
                    unlockContentHeightDelayed();
                } else {
                    if (isSoftInputShown()) {
                        lockContentHeight();
                        showEmotionLayout();
                        mEmojiView.setChecked(true);
                        unlockContentHeightDelayed();
                    } else {
                        showEmotionLayout();
                    }
                }
            }
        });
        return this;
    }

表情解析

问题分析:

1、如何将表情码和表情建立联系

2、如何给表情分页

3、如何将表情码转换成表情

将表情码和表情以键值对的形式建立联系
ArrayMap<String, Integer> emoJiMap = new ArrayMap<String,Integer>();

key(表情码)value(表情地址)

        emoJiMap.put("[emoji_1]",R.drawable.emoji_1);
        emoJiMap.put("[emoji_2]",R.drawable.emoji_2);
        emoJiMap.put("[emoji_3]",R.drawable.emoji_3);
        emoJiMap.put("[emoji_4]",R.drawable.emoji_4);
        emoJiMap.put("[emoji_5]",R.drawable.emoji_5);
        emoJiMap.put("[emoji_6]",R.drawable.emoji_6);
        emoJiMap.put("[emoji_7]",R.drawable.emoji_7);
        emoJiMap.put("[emoji_8]",R.drawable.emoji_8);
        emoJiMap.put("[emoji_9]",R.drawable.emoji_9);
        emoJiMap.put("[emoji_10]",R.drawable.emoji_10);
        emoJiMap.put("[emoji_11]",R.drawable.emoji_11);
        emoJiMap.put("[emoji_12]",R.drawable.emoji_12);
        emoJiMap.put("[emoji_13]",R.drawable.emoji_13);
        emoJiMap.put("[emoji_14]",R.drawable.emoji_14);
        emoJiMap.put("[emoji_15]",R.drawable.emoji_15);
        emoJiMap.put("[emoji_16]",R.drawable.emoji_16);
        emoJiMap.put("[emoji_17]",R.drawable.emoji_17);
        emoJiMap.put("[emoji_18]",R.drawable.emoji_18);
        emoJiMap.put("[emoji_19]",R.drawable.emoji_19);
        emoJiMap.put("[emoji_20]",R.drawable.emoji_20);
将表情面板的表情码用List进行保存
List<String> emojiList = new ArrayList<String>();
        emojiList.add("[emoji_1]");
        emojiList.add("[emoji_2]");
        emojiList.add("[emoji_3]");
        emojiList.add("[emoji_4]");
        emojiList.add("[emoji_5]");
        emojiList.add("[emoji_6]");
        emojiList.add("[emoji_7]");
        emojiList.add("[emoji_8]");
        emojiList.add("[emoji_9]");
        emojiList.add("[emoji_10]");
        emojiList.add("[emoji_11]");
        emojiList.add("[emoji_12]");
        emojiList.add("[emoji_13]");
        emojiList.add("[emoji_14]");
        emojiList.add("[emoji_15]");
        emojiList.add("[emoji_16]");
        emojiList.add("[emoji_17]");
        emojiList.add("[emoji_18]");
        emojiList.add("[emoji_19]");
        emojiList.add("[emoji_20]");
计算表情页
    public List<View> getPagers() {
        List<View> pageViewList = new ArrayList<>();
        //每一页表情的view
        mPageNum = (int) Math.ceil(mEmoJiResList.size() * 1.0f / EMOJI_PAGE_COUNT);
        for (int position = 1; position <= mPageNum; position++) {
            pageViewList.add(getGridView(position));
        }
        return pageViewList;
    }
表情分页
    public View getGridView(int position) {
        List mEmoJiList = new ArrayList<>();
        View containerView = View.inflate(mContext, R.layout.container_gridview, null);
        ExpandGridView eg_gridView = (ExpandGridView) containerView.findViewById(R.id.eg_gridView);
        eg_gridView.setGravity(Gravity.CENTER_VERTICAL);
        List<String> emojiPageList = null;
        if (position == mPageNum)//最后一页
            emojiPageList = mEmoJiResList.subList((position - 1) * EMOJI_PAGE_COUNT, mEmoJiResList.size());
        else
            emojiPageList = mEmoJiResList.subList((position - 1) * EMOJI_PAGE_COUNT, EMOJI_PAGE_COUNT * position);
        mEmoJiList.addAll(emojiPageList);
        //添加删除表情
        mEmoJiList.add("[删除]");

        final EmoJiAdapter mEmoJiAdapter = new EmoJiAdapter(mContext, position, mEmoJiList);
        eg_gridView.setAdapter(mEmoJiAdapter);
        eg_gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int positionIndex, long id) {
                String fileName = mEmoJiAdapter.getItem(positionIndex);
                if (fileName != "[删除]") { // 不是删除键,显示表情
                    showEmoJi(fileName);
                } else { // 删除文字或者表情
                    deleteContent();
                }
            }
        });
        return containerView;
    }
将表情面板的表情码转解析成表情
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = View.inflate(getContext(), R.layout.item_row_emoji, null);
        }

        ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_emoji);
        String fileName = getItem(position);
        Integer resId = EmoJiUtils.getEmoJiMap().get(fileName);
        if (resId != null) {
            Drawable drawable = getContext().getResources().getDrawable(resId);
            drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
            imageView.setImageResource(resId);
        }

        return convertView;
    }
输入框表情码转换成表情
    public static SpannableString parseEmoJi(Context context, String content) {

        SpannableString spannable = new SpannableString(content);
        String reg = "\\[[a-zA-Z0-9_\\u4e00-\\u9fa5]+\\]";//校验表情正则
        Pattern pattern = Pattern.compile(reg);
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            String regEmoJi = matcher.group();//获取匹配到的emoji字符串
            int start = matcher.start();//匹配到字符串的开始位置
            int end = matcher.end();//匹配到字符串的结束位置
            Integer resId = emoJiMap.get(regEmoJi);//通过emoji名获取对应的表情id

            if (resId != null) {

                Drawable drawable = context.getResources().getDrawable(resId);
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
                ImageSpan imageSpan = new ImageSpan(drawable, content);
                spannable.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }

        }
        return spannable;
    }
    private void showEmoJi(String fileName) {
        int selectionStart = mInputContainer.getSelectionStart();
        String body = mInputContainer.getText().toString();
        StringBuilder stringBuilder = new StringBuilder(body);
        stringBuilder.insert(selectionStart, fileName);
        mInputContainer.setText(EmoJiUtils.parseEmoJi(mContext, stringBuilder.toString()));
        mInputContainer.setSelection(selectionStart + fileName.length());
    }
表情删除
    private void deleteContent() {
        if (!TextUtils.isEmpty(mInputContainer.getText())) {
            int selectionStart = mInputContainer.getSelectionStart();//获取光标位置
            if (selectionStart > 0) {
                String body = mInputContainer.getText().toString();
                String lastStr = body.substring(selectionStart - 1, selectionStart);//获取最后一个字符
                if (lastStr.equals("]")) {//表情
                    if (selectionStart < body.length()) {//从中间开始删除
                        body = body.substring(0, selectionStart);
                    }
                    int i = body.lastIndexOf("[");
                    if (i != -1) {
                        String tempStr = body.substring(i, selectionStart);//截取表情码
                        if (EmoJiUtils.getEmoJiMap().containsKey(tempStr)) {//校验是否是表情
                            mInputContainer.getEditableText().delete(i, selectionStart);//删除表情
                        } else {
                            mInputContainer.getEditableText().delete(selectionStart - 1, selectionStart);//删除一个字符
                        }
                    } else {
                        mInputContainer.getEditableText().delete(selectionStart - 1, selectionStart);
                    }
                } else {//非表情
                    mInputContainer.getEditableText().delete(selectionStart - 1, selectionStart);
                }
            }
        }
    }

源码地址:

https://github.com/diycoder/EasyEmoji
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android实现 emoji 表情选择的 View,一般可以采用以下两种方式: 1. 使用第三方库 可以使用一些开源的第三方库,如: - Emoji-Keyboard:https://github.com/rockerhieu/emojicon - EmojiEditText:https://github.com/kimoBiko/EmojiEditText - EmojiCompat:https://developer.android.com/topic/libraries/support-library/packages#emoji-compat 这些库都提供了丰富的 emoji 表情选择界面和相关的实现方法,可以方便地集成到项目中。 2. 自定义 View 如果需要更灵活地实现 emoji 表情选择的 View,可以自己实现一个自定义 View。具体实现步骤如下: 1. 准备 emoji 表情资源 首先需要准备 emoji 表情的资源,可以自己设计或者使用现成的资源。将这些资源放在项目的 res 目录下。 2. 自定义 View 自定义 View 可以继承自 LinearLayout,包含一个 EditText 和一个 GridView。 在 EditText 的右侧添加一个按钮,点击按钮时显示 emoji 表情选择的 GridView,点击表情后将表情插入到 EditText 中。 3. 实现表情选择的 GridView GridView 中的每个表情可以使用 ImageView 显示,可以通过设置表情资源的 ID 来显示不同的表情。在表情选择界面的底部可以添加一个删除按钮,点击删除按钮可以删除 EditText 中的最后一个字符或者最后一个表情。 4. 实现点击表情插入到 EditText 的功能 当用户点击 GridView 中的表情时,可以将表情插入到 EditText 中。可以通过在 EditText 中插入一个 SpannableString 来实现,其中 SpannableString 中包含了表情的资源 ID。 以上就是实现 emoji 表情选择的 View 的一般步骤,可以根据具体需求进行自定义实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金戈鐡馬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值