仿微信表情輸入鍵盤(支持 Gif 表情圖文混排 )


簡介

自定義的表情輸入鍵盤在不少應用中都會有用到,譬如微信、QQ 等社交聊天軟件中更是不可缺乏的部分。本文將解析一下我的的自定義表情輸入控件庫 PandaEmoView 的實現和使用。java

該庫具備如下特色:

  • 支持 emoji 表情圖片
  • 支持 gif 動態表情輸入顯示
  • 支持單張貼圖表情(與微信收藏表情一致)
  • 支持題圖表情庫的添加刪除

效果圖:

效果圖

快速使用

引入庫

compile 'com.pandaq:PandaEmoView:1.0.0'
複製代碼

表情資源及配置文件

  • 默認的 emoji 和 gif 表情以及他們的配置文件是放在開發包 assets 目錄下的,若表情比較多比較大也可自行修改源碼在 APP 啓動時從服務器下載。

assets 目錄圖片

  • emoji 表情配置文件

config 截圖

  • 非自定義 sticker 配置文件(自定義 sticker 是沒有配置文件的)

sticker config 截圖

具體使用規則

與表情輸入控件相關的 EditText 必須使用 PandaEditText PandaEditText 只是重寫了 onKeyPreIme() 獲取按返回鍵的通知,繼承自 EditText 的控件可繼承 PandaEditText 自定義android

1.應用 Application 中進行全局參數配置git

private void configPandaEmoView() {
        new PandaEmoManager.Builder()
                .with(getApplicationContext()) // 傳遞 Context
                .configFileName("emoji.xml")// 配置文件名稱
                .emoticonDir("face") // asset 下存放表情的目錄路徑(asset——> configFileName 之間的路徑,結尾不帶斜槓)
                .sourceDir("images") // 存放 emoji 表情資源文件夾路徑(emoticonDir 圖片資源之間的路徑,結尾不帶斜槓)
                .showAddTab(true)//tab欄是否顯示添加按鈕
                .showStickers(true)//tab欄是否顯示貼圖切換按鍵
                .showSetTab(true)//tab欄是否顯示設置按鈕
                .defaultBounds(30)//emoji 表情顯示出來的寬高
                .cacheSize(1024)//加載資源到內存時 LruCache 緩存大小
                .defaultTabIcon(R.drawable.ic_default)//emoji表情Tab欄圖標
                .emojiColumn(7)//單頁顯示錶情的列數
                .emojiRow(3)//單頁顯示錶情的行數
                .stickerRow(2)//單頁顯示貼圖表情的行數
                .stickerColumn(4)//單頁顯示貼圖表情的列數
                .maxCustomStickers(30)//容許添加的收藏表情數
                .imageLoader(new IImageLoader() {
                    @Override
                    public void displayImage(String path, ImageView imageView) { // 加載貼圖表情的圖片加載接口
                        Picasso.with(getApplicationContext())
                                .load(path)
                                .fit()
                                .centerCrop()
                                .into(imageView);
                    }
                })
                .build(); //構建 PandaEmoManager 單利
    }
複製代碼

2.使用此控件的 Activity 在 manifest 文件中配置github

// 這句是必定要加上的。
android:windowSoftInputMode="adjustResize"
複製代碼

3.使用此控件的界面 xml 文件規則 佈局規則以下圖,lockView 便是咱們正常顯示內容的 View 它與表情輸入控件 PandaEmoView 屬於同一層級,父佈局必須爲縱向線性佈局,且設置 lockView 權重爲 1 ,PandaEmoView 高度包裹內容便可緩存

佈局規則說明
4.使用控件的 Activity Java 代碼設置

//界面控件初始化後 .attachEditText()綁定輸入控件

//初始化 KeyBoardManager,PandaEmoView.attachEditText() 必須在後調用

複製代碼

主要使用類及公有方法概覽

PandaEmoEditText

  • 表情輸入框繼承自 EditText 只對 onKeyIme() 進行復寫用於監聽輸入鍵盤或者軟鍵盤的彈出與關閉

PandaEmoView

  • 表情輸入控件 View 繼承自 RelativeLayout
方法 返回值 參數 描述
attachEditText(PandaEmoEditText input) void 表情輸入控件(如使用自定義 EditText 可直接繼承重寫) 綁定輸入控件
getAttachEditText() void 獲取當前表情控件綁定的輸入框控件
reloadEmos void position 重載表情控件數據後默認選中 Tab 的 position 添加或者刪除表情數據後重載刷新表情控件

PandaEmoManager

  • PandaEmoManager 爲核心配置類,表情控件的各類參數都經過此類的構造器進行配置
屬性 類型 描述 默認值
EMOT_DIR String 默認表情圖在 assets 中的路徑 (assets 目錄到配置 xml 文件之間的部分的路徑,demo 中的 face) face
SOURCE_DIR String EMOT_DIR 目錄下存放圖片資源的文件夾路徑 (demo 中的 images) source_default
STICKER_PATH String Sticker貼圖包的存放目錄,該目錄下的每個文件目錄都爲一個貼圖包 /data/data/< package name>/files/sticker
CACHE_MAX_SIZE int 加載表情 LruCache 緩存大小 1024
DEFAULT_EMO_BOUNDS_DP int 表情圖顯示大小(非貼圖表情) 30dp
defaultIcon int 表情 Tab 資源文件名 R.drawable.ic_default
mContext Context 上下文 null
mConfigFile String EMOT_DIR 目錄下的 emoji 配置文件名稱 emoji_default.xml
mIImageLoader IImageLoader Sticker 圖片加載器接口,加載方式外部傳入 null
MAX_CUSTOM_STICKER int 最大添加的自定義貼圖表情數 30
EMOJI_ROW int emoji 表情單頁行數 3
EMOJI_COLUMN int emoji 表情單頁列數 7
STICKER_ROW int sticker 表情單頁行數 2
STICKER_COLUMN int sticker 表情單頁列數 4
showAddButton boolean tab 欄是否顯示添加按鈕 ture
showSetButton boolean tab 欄是否顯示設置按鈕 ture
showStickers boolean tab 欄是否顯示貼圖按鈕(因此 sticker) ture
方法 返回值 參數 描述
init() void 無參 初始化 拼接 Sticker 路徑及建立自定義貼圖 文件夾 (STICKER_PATH + "/selfSticker" )
makePattern() Pattern 無參 建立 emoji 正則匹配器

剩餘方法都爲屬性值的 getter() setter() 不在贅述。bash

PandaEmoManager.Builder

  • PandaEmoManager 的構造器類,屬性及方法都與 PandaEmoManager 一一對應;

KeyBoardManager

  • KeyBoardManager 爲輸入法軟鍵盤與表情輸入控件協調管理類
屬性 類型 描述 默認值
SHARE_PREFERENCE_NAME String 用於存儲鍵盤高度的 SP 的名字 "EmotionKeyBoard"
SHARE_PREFERENCE_SOFT_INPUT_HEIGHT String 用於存儲鍵盤高度的 key "EmotionKeyBoard"
mActivity Activity 控件依附的 Activity 界面 null
mEmotionView PandaEmoView 當前管理的表情輸入控件 null
interceptBackPress boolean 是否攔截返回鍵 false
lockView View 鎖定高度的 View(即同一線性父佈局中,表情控件以外的佈局視圖) null
mOnEmotionButtonOnClickListener OnEmotionButtonOnClickListener 表情顯示控制按鈕監聽 null
mOnInputShowListener OnInputShowListener 監聽輸入欄的彈出與關閉 null
方法 返回值 參數 描述
with() KeyBoardManager Activity : 當前輸入控件依附的 Activity 初始化 KeyBoardManager 建立單例
bindToLockContent() KeyBoardManager lockView:切換須要鎖定高度的 View 賦值給內部屬性 lockView
bindToEmotionButton() KeyBoardManager View...: 多個 View 參數,切換控制按鈕 爲輸入控件綁定控制按鈕
setEmotionView() KeyBoardManager PandaEmoView: 被管理的表情控件 綁定當前管理的輸入控件(綁定的控件必須在此以前調用 PandaEmoView.attachEditText() 不然內部將會空指針)
interceptBackPress() boolean null 在 Activity 的 backPressd() 中檢查是否須要攔截返回鍵關閉輸入欄而不是退出界面
hideInputLayout() void null 供外部調用關閉輸入欄(不能未初始化直接調用)
showInputLayout() void null 供外部調用顯示輸入欄(不能未初始化直接調用)

EmoticonManager

  • EmoticonManager 爲 emoji 表情加載管理類,此類提供方法將資源文件根據配置文件加載進內存,方法大多數爲私有方法,源碼中可查看註釋。

StickerManager

  • StickerManager 爲 sticker 表情加載管理類,此類提供方法將資源文件根據配置文件加載進內存,與 EmoticonManager 相似

PandaEmoTranslator

  • PandaEmoTranslator 爲 emoji 表情 [文字] 轉表情的轉換工具類
方法 返回值 參數 描述
getInstance() PandaEmoTranslator null 獲取文本表情轉換器單例
setMaxGifPerView() void maxGifPerView: 每一個 TextView 控件最多顯示動態表情的個數 設置每一個 TextView 控件最多顯示動態表情的個數,超過此數所有顯示爲靜態表情
getMaxGifPerView() int null 獲取每一個 TextView 控件最多顯示動態表情的個數
makeGifSpannable() SpannableString classTag:PandaEmoView 依附的 ActivityTag(推薦使用 Activity.getLocalClassName());value : 待替換的文本 ;gif 運行回調(回調中須要刷新 TextView 重繪) 整段圖文混排,支持 gif 和靜態 emoji
makeEmojiSpannable() SpannableString classTag:PandaEmoView 依附的 ActivityTag(推薦使用 Activity.getLocalClassName());value : 待替換的文本 ;gif 運行回調(回調中須要刷新 TextView 重繪) 整段圖文混排,因此內容轉換爲靜態 emoji
resumeGif() void activityTag : makeGifSpannable() 傳入的 tag 名 開始 activityTag 對應的全部 gif 表情執行
pauseGif() void 暫停全部的 Gif 表情運行
clearGif() void activityTag : makeGifSpannable() 傳入的 tag 名 中止 activityTag 對應的全部 gif 表情執行,並將期從任務棧中移除

關於內存優化

由於表情,gif 表情,自定義貼圖,表情包貼圖這些都涉及到圖片資源加載到內存中。所以開發過程當中不可避免的也遇到了許多的內存優化相關的問題。服務器

工具

就地取材,直接使用 Android Studio 的 Monitors 工具能夠直觀的查看到應用運行過程當中內存的變化過程微信

優化點1 —— Gif 播放類的優化

  • 問題: 參考網上的 gif 圖文混排項目,雖然實現了 gif 與文字的圖文混排效果,但存在致命的缺陷。該項目中每個 gif 動態表情圖都有一個對應的 Runable 對象去執行 gif 圖片的逐幀播放,當一個表情重複輸入也會有新的 Runable 對象去執行這樣的操做,這樣作的後果就是當輸入的表情數量增長時,所消耗的內存是持續增加的。這顯然不能知足生產使用的需求。
  • 解決方案: 考慮到此處內存增長的緣由是讓表情動起來的 Runable 氾濫引發的,所以減小 Runable 的數量就是解決此處內存問題的關鍵。個人方案作的比較完全,整個應用 gif 表情這一起都交給一個 Runable 去處理,這個 Runable 在 PandaTranslator 中進行圖文轉化時會被初始化
// PandaTranslator 的 103 - 107 行 
 103                   if (mGifRunnable == null) {
 104                      mGifRunnable = new GifRunnable(gifDrawable, mHandler);
 105                   } else {
 106                      mGifRunnable.addGifDrawable(gifDrawable);
 107                   }
複製代碼

由於 PandaTranslator 是一個單例實現,因此在他初始化後 mGifRunnable 也將保持惟一性。不管是新建初始化仍是 addGifDrawable() 都是把 Gif 表情對象放入 GifRunnable 中的一個 Map 中。Map 的 key value 分別是表情控件依附的 Activity 的 LocalName 和 一個 AnimatedGifDrawable 的 List。在 GifRunnable 的 run 方法中會根據當前的 Activity 的 LocalName 去取出對應的 AnimatedGifDrawable 列表,遍歷執行並按第一張 gif 表情的幀間隔去刷新 Drawable 並觸發 TextView 刷新回調ide

@Override
    public void run() {
        isRunning = true;
        if (currentActivity != null) {
            List<AnimatedGifDrawable> runningDrawables = mGifDrawableMap.get(currentActivity);
            if (runningDrawables != null) {
                for (AnimatedGifDrawable gifDrawable : runningDrawables) {
                    AnimatedGifDrawable.RunGifCallBack listener = gifDrawable.getUpdateListener();
                    List<AnimatedGifDrawable.RunGifCallBack> runningListener = listenersMap.get(currentActivity);
                    if (runningListener != null) {
					// 避免一個 TextView 多個表情時重複添加回調
                        if (!runningListener.contains(listener)) {
                            runningListener.add(listener);
                        }
                    } else {
                        // 爲空時確定不存在直接添加
                        runningListener = new ArrayList<>();
                        runningListener.add(listener);
                        listenersMap.put(currentActivity, runningListener);
                    }
                    gifDrawable.nextFrame();
                }
                for (AnimatedGifDrawable.RunGifCallBack callBack : listenersMap.get(currentActivity)) {
                    if (callBack != null) {
                        callBack.run();
                    }
                }
                frameDuration = runningDrawables.get(0).getFrameDuration();
            }
        }
        mHandler.postDelayed(this, frameDuration);
    }
複製代碼

這樣就實現了全局使用一個 Runable 來執行 gif 動起來的任務,不一樣的界面也僅須要將該界面的 AnimatedGifDrawable 對象加入任務 Map 便可。工具

優化點2 —— 界面暫停或退出時 Gif 播放資源同步退出回收

上面說到的將 AnimatedGifDrawable 列表加入任務 Map,只進不出顯然是不科學的也會持續增長內存的消耗。咱們但願在 Activity 退出時能將將當前 Activity 的 AnimatedGifDrawable 列表銷燬移除,在界面不可見可是可能會恢復時(pause 狀態)暫停 Runable 的執行,減小資源消耗。因而 GifRunable 提供了以下三個方法給外部調用

/** * 使用了表情轉換的界面退出時調用,中止動態圖handler */
    public void clearHandler(String activityName) {
        currentActivity = null;
        //清除當前頁的數據
        mGifDrawableMap.remove(activityName);
        // 當退出當前Activity後沒表情顯示時中止 Runable 清除全部動態表情數據
        listenersMap.remove(activityName);
        if (mGifDrawableMap.size() == 0) {
            clearAll();
        }
    }

    private void clearAll() {
        mHandler.removeCallbacks(this);
        mHandler.removeCallbacksAndMessages(null);
        mGifDrawableMap.clear();
        isRunning = false;
    }

    /** * 啓動運行 */
    public void startHandler(String activityName) {
        currentActivity = activityName;
        if (mGifDrawableMap != null && mGifDrawableMap.size() > 0 && !isRunning) {
            run();
        }
    }
複製代碼

它的調用入口都在 PandaTranslator 中,而後咱們只需在使用到 PandaEmoView 或者直接在 BaseActivity 的 onResume(),onPause(),onDestory() 中分別調用如下三個方法:

PandaTranslator.getInstance().resumeGif(activityLocalName);

PandaTranslator.getInstance().pauseGif();

PandaTranslator.getInstance().clearGif(activityLocalName)
複製代碼

優化點3 —— 使用 LruCache 緩存 emoji 資源

根據 LRU 規則將表情 Gif 緩存,避免重複加載建立新對象。

本庫地址 PandaEmoView 歡迎 star 和提 issue

相關文章
相關標籤/搜索