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

本文介绍了如何在Android中实现表情键盘与系统软键盘的切换,包括控制表情键盘显示隐藏、解析表情、处理表情与非表情的删除,以及获取软键盘高度等关键步骤。通过绑定表情按钮,动态调整SoftInputMode和使用布局锁定来避免输入框抖动问题。同时,文章详细讲解了表情码与表情资源的映射和分页展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:

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

效果图

功能:

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

金戈鐡馬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值