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

这篇具有很好参考价值的文章主要介绍了Android一步一步教你实现Emoji表情键盘。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景:

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

效果图

android 表情键盘,Android,android,表情键盘,Emoji,聊天表情,View,Powered by 金山文档

功能:

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(表情地址)文章来源地址https://www.toymoban.com/news/detail-599407.html

        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

到了这里,关于Android一步一步教你实现Emoji表情键盘的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Midjourney:一步一步教你如何使用 AI 绘画 MJ

    一步一步如何使用 Midjourney 教程:教学怎么用 MJ? 原文:如何使用 Midjourney 教程 https://bysocket.com/saas-digital-marketing-channel/ Midjourney是一款使用文字描述来生成高质量图像的AI绘画工具。这篇文章主要介绍了Midjourney及其用途,并针对Midjourney的使用提供了一些指南。该工具可以帮

    2023年04月21日
    浏览(82)
  • 文本转语音-微软Azure-一步一步教你从注册到使用

    牙叔教程 简单易懂 他们的中文也许还行, 但是英文我试了都不满意, 我再网上搜到的我认为最好的是 但是丫真贵 Best Free Text To Speech Voice Reader | Speechify 现在的汇率是 139 × 6.91 = 960.49 一年一千块, 好像还行哈, 但是没卡呀, 擦, 比来比去, 还是微软Azure性价比最高, 没有微软Azure的

    2024年02月07日
    浏览(44)
  • Python 一步一步教你用pyglet仿制鸿蒙系统里的时钟

    目录 鸿蒙时钟 1. 绘制圆盘 2. 创建表类 3. 绘制刻度 4. 刻度数值 5. 添加指针 6. 转动指针 7. 联动时间 8. 时钟走动 本篇将用python pyglet库复刻华为手机鸿蒙系统闹钟程序的时钟,先在上图中抓取出时分秒针及刻度、表盘的颜色RGB值: bHour = (42, 43, 48, 255) bMinute = (70, 71, 75, 255) rSe

    2024年03月12日
    浏览(68)
  • Python 一步一步教你用pyglet制作“彩色方块连连看”游戏

    目录 彩色方块连连看 第一步 第二步 第三步 第四步 第五步 第六步 第七步 动态效果展示 小结 本篇除了介绍怎样用pyglet制作连连看游戏,还将介绍如果使用自定义库colorlib,用它来描绘游戏中多种颜色的彩色方块。自定义库colorlib的由来,另请阅读《python 教你如何创建一个自

    2024年04月08日
    浏览(57)
  • Python 一步一步教你用pyglet制作“彩色方块连连看”游戏(续)

    上期讲到相同的色块连接,链接见: Python 一步一步教你用pyglet制作“彩色方块连连看”游戏-CSDN博客 续上期,接下来要实现相邻方块的连线: 首先来进一步扩展 行列的类: class RC:     def __init__(self, r=0, c=0):         self.r, self.c = r, c     def __repr__(self):         return f\\\'Rc

    2024年04月08日
    浏览(65)
  • Python 一步一步教你用pyglet制作可播放音乐的扬声器类

    目录 扬声器类 1. 绘制喇叭 2. 扬声器类 3. 禁音状态  4. 设置状态 5. 切换状态 6. 播放音乐 本篇将教你用pyglet画一个小喇叭,如上图。这里要用到pyglety库shapes模块中的圆弧Arc和多边形Pylygon画出这个扬声器的图片: Arc(x, y, radius, segments=None, angle=6.283185307179586, start_angle=0, closed=

    2024年03月10日
    浏览(59)
  • 一步一步教你如何使用 Visual Studio Code 编译一段 C# 代码

    以下是一步一步教你如何使用 Visual Studio Code 编写使用 C# 语言输出当前日期和时间的代码: 1、下载并安装 .NET SDK。您可以从 Microsoft 官网下载并安装它。 2、打开 Visual Studio Code,并安装 C# 扩展。您可以在 Visual Studio Code 中通过扩展菜单安装它。 3、打开 Visual Studio Code 中的文

    2024年02月11日
    浏览(51)
  • 【沐风老师】一步一步教你在3dMax中进行UVW贴图和展开UVW的方法

    将简单或程序材质应用于对象并不难。但是当表面需要在其上显示某种纹理时,它会变得更加复杂。任何纹理贴图都放在材质的 Diffuse 插槽中,但渲染的结果可能无法预测。这就是为什么我们需要了解 3DMAX 如何将纹理应用于 3D 对象,什么是 UVW 贴图,以及为什么要“展开”它

    2024年02月04日
    浏览(63)
  • FastAPI + NGINX + Gunicorn:一步一步教你部署一个高性能的Python网页应用

    部署一个 FastAPI 应用到你的服务器是一项复杂的任务。如果你对 NGINX 、 Gunicorn 和 Uvicorn 这些技术不熟悉,可能会浪费大量的时间。如果你是刚接触 Python 语言不久或者希望利用 Python 构建自己的Web应用程序,本文的内容可能会让你第一次部署时更节省时间。 FastAPI 是用于开发

    2024年02月05日
    浏览(65)
  • 一步一步教你如何白嫖谷歌云Google Cloud服务器$300美金羊毛

    我们都知道,Depay(现在改名为Dupay了)卡平常可以用于微信,支付宝,美团消费,直接用USDT做日常小额消费,还免收手续费,小额的话,这点还是很舒服的。 但其实,Depay卡的用途远不止此,平常可以多挖掘挖掘。今天教大家如何用Depay卡白嫖谷歌云服务器。申请成功后随即可

    2024年02月04日
    浏览(129)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包