一、 说明


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

二、功能


1、如何控制表情键盘与输入法的切换
2、如何解析表情
3、如何处理表情与非表情的删除

三、实现


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

表情键盘与输入法切换
博主查了一下相关资料,有如下方案

方案一:动态改变SoftInputMode
软键盘显示时将SoftInputMode设置为「stateVisible|adjustResize」,表情键盘显示时调整为「adjustPan」

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

博主在观察QQ、微信、微博、陌陌后发现,他们的表情键盘和软键盘切换,并不会导致聊天内容(ListView、RecyclerView)的跳动,基本就可以推测SoftInputMode就是adjustsPan(SoftInputMode含义)
明确了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() {@Overridepublic 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() {@Overridepublic void run() {((LinearLayout.LayoutParams) mContentView.getLayoutParams()).weight = 1.0F;}}, 200L);}

表情面板控制

 public EmotionInputDetector bindToEmotionButton(final CheckBox emotionButton) {mEmojiView = emotionButton;emotionButton.setOnClickListener(new View.OnClickListener() {@Overridepublic 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<>();//每一页表情的viewmPageNum = (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());elseemojiPageList = 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() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int positionIndex, long id) {String fileName = mEmoJiAdapter.getItem(positionIndex);if (fileName != "[删除]") { // 不是删除键,显示表情showEmoJi(fileName);} else { // 删除文字或者表情deleteContent();}}});return containerView;}

将表情面板的表情码转解析成表情

  @Overridepublic 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名获取对应的表情idif (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);}}}}

四、成果


github:
EasyEmoji

一步一步教你实现Emoji表情键盘相关推荐

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

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

  2. 通过脚本案例学习shell(五) 通过创建DNS脚本一步一步教你将一个普通脚本规范到一个生产环境脚本...

    通过脚本案例学习shell(五) 通过创建DNS脚本一步一步教你将一个普通脚本规范到一个生产环境脚本   版权声明: 本文遵循"署名非商业性使用相同方式共享 2.5 中国大陆"协议 ...

  3. 教你一步一步用C语言实现sift算法、上

    原文:http://blog.csdn.net/v_july_v/article/details/6245939 引言:     在我写的关于sift算法的前倆篇文章里头,已经对sift算法有了初步的 ...

  4. 一步一步教你使用AgileEAS.NET基础类库进行应用开发-基础篇-基于接口驱动的数据层...

    系列回顾 在前面的文章中,我用了大量的篇幅对UDA及ORM的使用进行了讲解和演示,我们已经知道并熟悉的使用UDA和ORM构建简单的应用,AgileEAS.NET在应用的纵向结构上建议使用分层结构,提出 ...

  5. include_fns.php_一步一步教你用PHP+MySql筹建网站 No.3 管理页面_mysql

    一步一步教你用PHP+mysql搭建网站 No.3 管理页面 先来看一下本篇blog将要介绍的内容. 我们的主页面已经搭建完成了,然后左边的navigation里面的大部分内容也都能点击了,只剩下&q ...

  6. 超级简单:一步一步教你创建一小型的asp.net mvc 应用程序

    超级简单:一步一步教你创建一小型的asp.net mvc 应用程序 这本教程中将帮助你创建一个小型的asp.net mvc示例. 在本教程中,我们将创建自己的 Model , View 和Contro ...

  7. 一步一步教你抓数据——用.net精确提取网站数据的通用方法 [转]

    一步一步教你抓数据--用.net精确提取网站数据的通用方法 [转] 2008年02月23日 星期六 16:53 具体实现思路: 1 首先用WebClient类下载网页源码 public static ...

  8. C语言实现寻找极值点,九之再续:教你一步一步用c语言实现sift算法、上

    教你一步一步用c语言实现sift算法.上 作者:July.二零一一年三月十二日 出处:http://blog.csdn.net/v_JULY_v 参考:Rob Hess维护的sift 库 环境:win ...

  9. 一步一步教你实现iOS音频频谱动画(一)

    如果你想先看看最终效果再决定看不看文章 -> bilibili 示例代码下载 第二篇:一步一步教你实现iOS音频频谱动画(二) 基于篇幅考虑,本次教程分为两篇文章,本篇文章主要讲述音频播放和频谱 ...

  10. 通过Dapr实现一个简单的基于.net的微服务电商系统(十一)——一步一步教你如何撸Dapr之自动扩/缩容...

    上一篇我们讲到了dapr提供的bindings,通过绑定可以让我们的程序轻装上阵,在极端情况下几乎不需要集成任何sdk,仅需要通过httpclient+text.json即可完成对外部组件的调用,这样 ...

最新文章

  1. 论文返修与校对的经验教训
  2. mysql 5000万条数据库_1亿条数据如何分表100张到Mysql数据库中(PHP)
  3. esxi添加网卡驱动
  4. 错误处理:one of the variables needed for gradient computation has been modified by inplace operation
  5. 简单易懂的多线程(通过实现Runnable接口实现多线程)
  6. Linux运维:ulimit命令修改打开文件的句柄数
  7. php如何修改xml中element值,php修改xml节点的值
  8. Java04异常、断言、日志和调试
  9. 【Java进阶营】Java多线程基础学习(一)
  10. python能做哪些方面的准备_学习pyhton需要做哪些准备工作
  11. java实时检测_JAVA 实时检测二进制流字符集
  12. 将图的广度优先遍历在邻接矩阵和邻接表存储结构上分别实现_《青岛大学-王卓-数据结构》B站学习...
  13. python网格搜索核函数_python机器学习——超参数网格搜索
  14. 云计算与大数据——云计算的特点
  15. 智能云亮相百度世界2020:重磅发布和升级十大产品,加速AI新基建
  16. java伪随机数概率_抽奖伪随机数生成器(Java)
  17. Zbrush 4R7 P3中给类模型怎么快速隐藏
  18. 用java调用oracle存储过程和函数
  19. 聚焦新零售 阿里云重磅推出零售云 1
  20. Win10环境下基于Hexo的静态博客环境搭建,及其阿里云部署

热门文章

  1. Android基站定位源代码
  2. 武汉大学惯性导航课程精要
  3. word2016自带公式编辑器转换成mathtype类型公式,以及设置公式大小
  4. java实现五子棋_Java的五子棋实现
  5. linux使用dwc串口,linux自带usb gadget设备驱动应用
  6. kettle软件的使用
  7. C++之STL-vector-string-list-deque-queue-map-文件操作
  8. 编译的html帮助文件(.chm)打不开,chm文件无法打开怎么办
  9. Java实现zip文件压缩与解压缩--附完整代码
  10. 用.bat文件切换固定IP地址和自动IP地址