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