1. 程式人生 > >一步一步教你實現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() {
            @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);
                }
            }
        }
    }

四、成果

這裡寫圖片描述