Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

目錄:

1、導讀;
2、Android 輸入法開發簡介及流程;
3、鬥圖 APP 開發介紹;
4、鬥圖 APP 功能優化;
5、總結

1、導讀

微信鬥圖的應用有很多,但大部分都是通過微信分享來實現的,需下載 APP,下載表情並分享到微信聯繫人,操作步驟複雜。而基於輸入法的微信鬥圖就少了不少操作,現在市面上的輸入法大都有鬥圖模塊,然而有些強迫症患者,對第三方輸入法的鬥圖模塊設計並不滿意,或者操作步驟依然複雜、有捆綁模塊、不喜歡輸入法有廣告、需要讀取隱私信息等各種原因,就是不想用不喜歡的第三方輸入法,基於這個需求,可以把鬥圖模塊單獨抽出來,製作一個專注於鬥圖的輸入法 APP。

通過分析市面上已有的輸入法鬥圖模塊可以得知微信的一個隱藏功能,就是在聊天輸入框輸入類似/storage/emulated/0/Android/data/cache/a.gif的圖片文件路徑時,微信會自動解析圖片,並彈出是否發送表情的確認彈窗,點擊確認就直接發送了圖片,如果是 gif 動圖則直接轉換成表情。所以我們只需在輸入法面板中通過關鍵字搜索表情後展示表情列表,直接點擊表情上屏圖片路徑,即可實現自動發送。那麼問題的關鍵就在於如何構建一個輸入法項目,最後爲了操作更方便,可以使用輔助功能提升用戶體驗。

2、輸入法開發

1.API簡介

簡單來說,開發一個輸入法,只需要用到一個核心類和幾個可有可無的輔助類。

核心類是InputMethodService,一個輸入法幾乎所有的功能都是由它來實現的,包括鍵盤界面的搭建、鍵盤語言的切換、拼音漢字的轉換、候選詞的展示、文字的上屏等各種邏輯都通過這個類來實現。InputMethodService類有如下幾個主要方法來管理輸入法服務的生命週期:

  • onCreate():輸入法開始創建,內部已經實現設置 theme、創建 window、填充 root view、設置佈局方式等,我們也可以在此處做一些初始化操作,但一定不要忘了調用 supper.onCreate();

  • onCreateInputView():返回一個 view 作爲輸入法的鍵盤佈局,通常這個view 是由KeyboardView 和 Keyboard 兩個輔助類生成,當然也完全可以自定義。切換一次輸入法只會調用一次;

  • onCreateCandidatesView(): 返回一個 view 來展示候選詞,這個 view 可有可無,會覆蓋到應用上方,一般用半透明的背景,但市面上的輸入法一般都用來顯示拼音部分,而把候選詞放入 InputView。同樣切換一次輸入法只會調用一次;

  • onStartInputView(EditorInfo): 開始輸入的時候調用,每次喚起鍵盤或切換 EditText 都會調用,並把 EditText 的 EditorInfo 傳過來,輸入法要根據 EditorInfo 的信息判斷中英文、數字、回車鍵類型等,來展示不同的鍵盤,也就是動態切換 InputView 的佈局;

  • onFinishInput(): 輸入結束的時候調用,此時可以做一些 reset 操作,比如隱藏 CandidatesView,恢復 InputView 爲默認佈局等;

  • onDestroy(): 一般在切換其他輸入法的時候會被調用,實測輸入法不用一段時間,系統也會暫時殺掉進程,此時也會被調用。

可能有同學對上面提到的InputViewCandidatesView到底是什麼還有些疑問,下面這張圖可以表達地很清楚:

Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

而這些方法定義的生命週期則可以通過Android Developers官方的一張圖來深入理解,相信每篇輸入法相關的文章都會有這張經典的圖:

Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

InputMethodService中,負責與正在輸入的APP交互的是其持有的InputConnection對象,可通過getCurrentInputConnection()方法獲取,InputConnection有如下幾個常用的方法:

  • getTextBeforeCursor(n): 獲取光標前 n 個字符;

  • getTextAfterCursor(n): 獲取光標後 n 個字符;

  • getSelectedText(): 獲取已經被選中的字符;

  • deleteSurroundingText(beforeLength, afterLength): 刪除光標前後的字符;

  • commitText(text): 提交 text 到 APP 的輸入框;

  • sendKeyEvent(keyEvent): 發送特殊的按鍵 code,如回車、退格等。

再說說上文中提到的兩個輔助類KeyboardViewKeyboard,已經提到,InputView可以是一個KeyboardViewKeyboardView內部已經封裝了一些鍵盤的通用功能,比如特殊按鍵(回車、退格等)的發送、滑動手勢功能、長按鍵盤功能、按鍵音的播放等等。而KeyboardView只是一個空的view它的佈局是沒有確定的,查找它的代碼我們發現其中有一個成員Keyboard,這就是另外一個輔助類,Keyboard的主要任務就是承載特定的鍵盤佈局,如中英文鍵盤、數字鍵盤、符號鍵盤等,並且將佈局中的按鍵和按鍵內容的KeyCode對應起來,通常這種對應關係可以用一個xml文件來定義(如圖 2.1.3),在構造Keyboard的時候去加載這個xml文件,這樣KeyboardView就持有Keyboard所有按鍵的引用,在按下某個按鍵時,通過已設置的OnKeyboardActionListener將按鍵對應的KeyCode傳到InputMethodService(要記得,此時KeyboardView已經是InputMethodService持有的InputView),或者經過處理——比如將拼音處理成漢字——並將處理後的文字發送給InputMethodServiceInputMethodService調用commitText()方法,將文字上屏到當前 APP 聚焦的EditText

Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

對於KeyboardViewKeyboard這兩個類的使用,Google官方有一個很好的實例,感興趣的同學可以下載來研究一下:SoftKeyboard Sample

2. 一般輸入法的開發步驟

接下來介紹一下一個輸入法完整的開發和配置,當然了我們的微信鬥圖APP因爲不涉及文字的處理和輸入,就少了很多步驟,瞭解了完整的流程,再來做鬥圖APP就不在話下了。

2.1 新建項目與配置

輸入法應用和普通的APP沒有什麼大的區別,像平常一樣新建項目即可,然而要讓系統知道這是一個輸入法應用,則需要做一些配置。

首先新建一個類SoftInputService繼承自InputMethodService,內容可以先留空。然後新建一個SettingsActivity作爲輸入法的設置界面,同樣先不用寫內容。接下來就是配置了,在AndroidManifest中如下配置SoftInputService

<service
    android:name="com.package.InputService"
    android:permission="android.permission.BIND_INPUT_METHOD">
    <intent-filter>
        <action android:name="android.view.InputMethod" />
    </intent-filter>
    <meta-data
        android:name="android.view.im"
        android:resource="@xml/method" />
</service>

系統檢測到android.view.InputMethod這個action表明本應用是一個輸入法,同時檢測是否有android.permission.BIND_INPUT_METHOD這個權限。後面的meta-data則是對輸入法的配置,其中的@xml/method如下:

<input-method xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="com.package.SettingsActivity"
    android:supportsSwitchingToNextInputMethod="true">
</input-method>

顯然settingsActivity就是配置輸入法的設置界面,安裝後在系統設置中的語言和輸入法中可以看到我們的輸入法,點擊後就會進入這個設置界面,如果修改了SettingsActivity的名稱或路徑,一定要把這裏也同步一下,否則系統找不到類就無法跳轉。而supportsSwitchingToNextInputMethod表示是否想要在我們的輸入法內切換輸入法。

至此,如果打包安裝,我們就能在系統設置中看到我們的輸入法,但還沒有實際的功能。

2.2 實現鍵盤佈局與文字上屏

前面提到過,可以用KeyboardViewKeyboard來實現簡單的鍵盤佈局,所以SoftInputService中的onCreateInputView()方法可以返回一個KeyboardView對象,在返回前要設置好Keyboard以及監聽:

@Override
public View onCreateInputView() {
    mKeyboard = new Keyboard(getApplicationContext(), R.xml.qwerty);
    mInputView = new KeyboardView(getApplicationContext(), null);
    mInputView.setOnKeyboardActionListener(this);
    mInputView.setKeyboard(mKeyboard);
    return mInputView;
}

Keyboard承載着鍵盤按鍵和KeyCode的對應關係,通過構造方法填充xml文件來實現,以下是一個完整的QWERTY鍵盤的xml映射:


<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
    android:keyWidth="10%p"
    android:horizontalGap="0px"
    android:verticalGap="0px"
    android:keyHeight="@dimen/key_height">
    <Row>
        <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
        <Key android:codes="119" android:keyLabel="w"/>
        <Key android:codes="101" android:keyLabel="e"/>
        <Key android:codes="114" android:keyLabel="r"/>
        <Key android:codes="116" android:keyLabel="t"/>
        <Key android:codes="121" android:keyLabel="y"/>
        <Key android:codes="117" android:keyLabel="u"/>
        <Key android:codes="105" android:keyLabel="i"/>
        <Key android:codes="111" android:keyLabel="o"/>
        <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
    </Row>
    <Row>
        <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p" android:keyEdgeFlags="left"/>
        <Key android:codes="115" android:keyLabel="s"/>
        <Key android:codes="100" android:keyLabel="d"/>
        <Key android:codes="102" android:keyLabel="f"/>
        <Key android:codes="103" android:keyLabel="g"/>
        <Key android:codes="104" android:keyLabel="h"/>
        <Key android:codes="106" android:keyLabel="j"/>
        <Key android:codes="107" android:keyLabel="k"/>
        <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
    </Row>
    <Row>
        <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift" 
             android:keyWidth="15%p" android:isModifier="true"
             android:isSticky="true" android:keyEdgeFlags="left"/>
        <Key android:codes="122" android:keyLabel="z"/>
        <Key android:codes="120" android:keyLabel="x"/>
        <Key android:codes="99" android:keyLabel="c"/>
        <Key android:codes="118" android:keyLabel="v"/>
        <Key android:codes="98" android:keyLabel="b"/>
        <Key android:codes="110" android:keyLabel="n"/>
        <Key android:codes="109" android:keyLabel="m"/>
        <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" 
             android:keyWidth="15%p" android:keyEdgeFlags="right"
             android:isRepeatable="true"/>
    </Row>
    <Row android:rowEdgeFlags="bottom">
        <Key android:codes="-3" android:keyIcon="@drawable/sym_keyboard_done" 
             android:keyWidth="15%p" android:keyEdgeFlags="left"/>
        <Key android:codes="-2" android:keyLabel="123" android:keyWidth="10%p"/>
        <Key android:codes="-101" android:keyIcon="@drawable/sym_keyboard_language_switch"
             android:keyWidth="10%p"/>
        <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space" 
             android:keyWidth="30%p" android:isRepeatable="true"/>
        <Key android:codes="46,44" android:keyLabel=". ,"
             android:keyWidth="15%p"/>
        <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_return" 
             android:keyWidth="20%p" android:keyEdgeFlags="right"/>
    </Row>
</Keyboard>

Keyboard已填充好,那麼接下來調用KeyboardView.setKeyboard(Keyboard)方法,把Keyboard傳入KeyboardView,此時KeyboardView會收集Keyboard的所有按鍵的映射關係及所在位置(從xml文件中的keyWidthkeyHeight屬性可看出,每個按鍵的位置已然固定),在點擊按鍵時,KeyboardView調用getKeyIndices(x, y)方法,根據點擊屏幕的位置,計算出點擊的是哪個按鍵,再用已設置好的OnKeyboardActionListener把輸出的文字回調到SoftInputServiceSoftInputService收到文字就可以調用InputConnection.commitText(text)將文字上屏到正在輸入的APP了。至此,一個具有輸入英文、數字、符號最簡單功能的輸入法已經完成。

2.3 複雜功能輸入法的實現

當然市面上任何一款輸入法APP都不會這麼簡單,但我們知道了原理,複雜的功能也能一一實現。

首先我們發現用來展示候選詞的CandidatesView並沒有用上,這個懸浮的view我們可以在輸入時,在SoftInputService中調用setCandidatesViewShown(show)方法來動態的顯示和隱藏,而展示內容則完全可以自定義,一般也就是展示一個預測輸入的文字或詞語的列表。

至於怎樣將拼音轉成可能的漢字,這就需要藉助一些詞庫來實現,各大輸入法都有自己的詞庫,甚至都有自己的雲詞庫,在輸入時上傳到網絡,可以匹配一些網絡流行語等。這些操作,都可以封裝在KeyboardView中或用單獨的計算模塊來實現。

而常見的26鍵鍵盤和九宮格鍵盤,則可以通過修改KeyboardView來動態實現,不管是怎樣的佈局,最終commit出來的文字都是計算處理之後的,而不像上面簡單的放在xml裏面實現,由於KeyboardView功能上的侷限性和外觀簡單的原因,成熟的輸入法幾乎都是自己定義InputView而不是使用簡單的KeyboardView

3、微信鬥圖 APP

我們已經知道在微信輸入框中輸入一個圖片路徑,就可以直接發送這個圖片,那麼鬥圖APP的關鍵在於獲取一個表情列表,展示在鍵盤佈局中,點擊圖片表情時將圖片的本地緩存路徑作爲文本commit出來。

對於表情來源,我們可以從現有的各大表情網站抓取,比如搜狗表情、鬥圖啦、鬥圖終結者等網站,都有大量的表情資源,通過Google Chrome的檢查功能或抓包操作可以得知用關鍵字搜索表情的接口,從而很輕鬆地獲取到這些網站的資源(僅供學習交流使用,切勿商用或惡意抓取)。

表情圖片的顯示,我們可以用一個簡單的GridView來展示,如何獲取圖片本地緩存的路徑,可以查詢主流圖片加載框架(Glide、Fresco等)的API。

接下來我們會發現一個問題,就是搜索表情的關鍵詞該如何傳入。因爲我們APP本身就是一個輸入法,不可能在輸入法內部切換到其他的輸入法來輸入文字,而我們自己的輸入法又不支持輸入文字(實現輸入漢字着實有些複雜,我們將重點放到鬥圖上)。

這個問題有多種方法變通來實現,首先就是可以在輸入法內開啓一個Activity,在Activity內喚出正常的輸入法輸入文字,關閉時切換回鬥圖輸入法並回調已輸入的文字,這種方法的問題就是輸入法應用允許切換到其他輸入法,但不允許切換回自己的輸入法,哪怕自己的在自己的輸入法存活的Service中操作,也是無效的。雖然可以藉助輔助功能來實現,但輸入法來回切換在視覺上的表現並不好。

另一種方法就是在微信輸入框內輸入好想要搜索的詞,再調出鬥圖輸入法來讀取這個已經輸入的詞。輸入法讀取聚焦的輸入框是被允許的,調用方式就是上文提到過的InputConnection.getTextBeforeCursor(n)來實現,取到搜索詞後就可以去各表情包網站抓取相關表情並展示,此時我們可以調用InputConnection.deleteSurroundingText()來刪掉輸入框中的搜索詞,因爲我們不再需要它,而且要避免手動刪除來提升用戶體驗。

我們可以設置GridViewOnItemClickListener或自定義監聽來回調點擊表情對應的圖片路徑,然後調用InputConnection.commitText(text)方法輸入到微信,此時微信會自動解析並彈出一個確認對話框(圖 3.1),點擊發送即可發送圖片,gif圖則會自動解析成動態的微信表情發送。

Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

4、鬥圖功能優化

以上的成果基本可以讓你在鬥圖過程中立於不敗之地,但對於追求極致體驗的同學,還有待優化之處,以下介紹兩點可以優化的地方,都需要用到輔助功能的服務。

第一個問題,微信彈出對話框後,還要點擊確認,多這一步操作,怎樣做到點擊表情立即發送呢,這時我們想到了AccessibilityService,通過它可以實現自動點擊。由於AccessibilityService不是今天的重點,具體怎麼使用同學們可以 Google 一下,下面放上AccessibilityService實現部分的代碼:


@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    CharSequence packageName = event.getPackageName();
    AccessibilityNodeInfo root = getRootInActiveWindow();
    if (root != null) {
        if ("com.tencent.mm".equals(packageName.toString())) { // 微信包名
            long currentTime = System.currentTimeMillis(); // 獲取當前時間
            long commitTextTime = SharedPrefUtil.getLong
                    (Constants.KEY_TIMESTAMP_ASSIST, 0); // 獲取圖片路徑提交時間
            if (currentTime - commitTextTime < 500) { // 圖片路徑提交後 500ms 內則觸發
                List<AccessibilityNodeInfo> confirm = 
                        root.findAccessibilityNodeInfosByText("確定"); // 找到確認按鈕
                if (confirm != null && confirm.size() > 0) {
                    confirm.get(0).performAction(ACTION_CLICK); // 觸發點擊事件
                }
            }
        }
        root.recycle();
    }
}

我們知道AccessibilityService是根據文本來找控件的,而微信中有“確定”二字的控件肯定不止一個,所以我們可以在commit圖片路徑之後存下一個時間戳,檢測控件時獲取當前的時間戳,這兩個時間戳的間隔在一定範圍內(此處定爲 500ms),就足以說明這個“確定”就是微信彈出的確定發送圖片的按鈕。此時就實現了自動發送,不過在使用過程中千萬不要手抖,避免發錯表情而引起尷尬。

第二個問題,在微信輸入框內輸入好搜索詞之後,我們得下拉通知欄,點擊通知欄“選擇輸入法”,再點擊彈出的輸入法選擇器才能完成切換輸入法,需要至少一個滑動和兩個點擊三步操作,能不能一步到位呢?當然能。同樣是利用AccessibilityService來實現:

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    CharSequence packageName = event.getPackageName();
    if ("com.tencent.mm".equals(packageName.toString())) {
        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) {
            InputMethodManager inputMethodManager = (InputMethodManager)
                    App.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (inputMethodManager != null) {
                inputMethodManager.showInputMethodPicker();
            }
        }
    }
    AccessibilityNodeInfo root = getRootInActiveWindow();
    if (root != null) {
        if ("android".equals(packageName.toString())) {
            List<AccessibilityNodeInfo> infos = root.findAccessibilityNodeInfosByText("Gifin");
            if (infos != null && infos.size() > 0) {
                infos.get(0).getParent().performAction(ACTION_CLICK);
            }
        }
        root.recycle();
    }
}

代碼前半段,檢測到在微信內長按操作則彈出輸入法選擇器,代碼後半段,檢測到輸入法選擇器彈出則選擇名爲“Gifin”的輸入法,即我們自己的輸入法。這樣一來,當我們用正常的輸入法輸入好搜索詞之後,只需長按自己的頭像,AccessibilityService會幫我們切換成鬥圖輸入法,並開始搜索展示搜索結果,再點擊結果直接發送表情一氣呵成。

其他的我們可以自由發揮,比如加入搜索歷史,使用多個圖源,加入收藏夾,保存到本地等功能。

Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

5、總結

到此處,我們可以說一句“鬥圖我從來沒輸過”,這一點也不吹牛,目前市面上鬥圖最快最便捷的就是輸入法類APP,而體驗過某輸入法後,發現實現一個完整的鬥圖過程,也需要至少5步操作,現在我們只需要輸入搜索詞、搜索並展示、點擊發送3步就可以完成。

當然了,對於很多同學來說,鬥圖不是目的,學習技術纔是重點,雖然Google上輸入法相關的文章一搜一大堆,但能結合實際體驗的並不多,也許這個APP的技術含量在某些大牛看來並不高,但對於初學者來說,或許能在衆多教程中找到一個更容易接受的角度和方式,那這也是本文的意義所在。學習輸入法相關知識再次強烈推薦Google官方示例SoftKeyboard Sample。

最後,生有涯而知無涯,如有紕漏之處還望廣大讀者批評指正,本APP源碼已開源至GitHub,同時也說明了一些坑,歡迎感興趣的同學前來Star。

Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

最後對於程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們!

這裏附上上述的技術體系圖相關的幾十套騰訊、頭條、阿里、美團等公司19年的面試題,把技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由於篇幅有限,這裏以圖片的形式給大家展示一部分。

相信它會給大家帶來很多收穫:

Android輸入法開發這樣製作一個微信鬥圖APP,鬥圖就再也沒輸過!

上述【高清技術腦圖】以及【配套的架構技術PDF】可以 加我wx:X1524478394 免費獲取

當程序員容易,當一個優秀的程序員是需要不斷學習的,從初級程序員到高級程序員,從初級架構師到資深架構師,或者走向管理,從技術經理到技術總監,每個階段都需要掌握不同的能力。早早確定自己的職業方向,才能在工作和能力提升中甩開同齡人。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章