安卓中的虛擬鍵盤實現,KeyEvent的事件分發、處理機制。EditText是如何將KeyEvent事件轉爲字符輸入的?

目錄

一、實現一個可以模擬輸入的軟鍵盤

二、問題:點擊軟鍵盤,沒有任何反應,輸入框沒有填入字符

原因:傳入小鍵盤鍵碼,和大鍵盤鍵碼,得到的結果不一致

三、爲什麼在前面經過測試的其他界面中,軟鍵盤卻又可以正常錄入字符呢?

原因:使用了不同的KeyListener實現類

安卓KeyEvent的處理機制總結:


一、實現一個可以模擬輸入的軟鍵盤

一開始,我們的需求是在用戶經常使用的部分界面中,增加虛擬軟鍵盤,減少用戶對於外接鍵盤的依賴

如圖,在整單改價界面右側增加了方便用戶快捷輸入的軟鍵盤,用戶不需要使用外接鍵盤,即可完成常見的商品改價等操作。

那麼這個代碼邏輯實現起來比較簡單,因爲業務中有許多類似界面需要使用該軟鍵盤功能,所以我們將它單獨封裝爲一個View:

        mView = View.inflate(context, R.layout.res_keypad_view, this)
        val map = hashMapOf<View, Int>()
        map[tv_num_1] = KeyEvent.KEYCODE_NUMPAD_1
        map[tv_num_2] = KeyEvent.KEYCODE_NUMPAD_2
        map[tv_num_3] = KeyEvent.KEYCODE_NUMPAD_3
        map[tv_num_4] = KeyEvent.KEYCODE_NUMPAD_4
        map[tv_num_5] = KeyEvent.KEYCODE_NUMPAD_5
        map[tv_num_6] = KeyEvent.KEYCODE_NUMPAD_6
        map[tv_num_7] = KeyEvent.KEYCODE_NUMPAD_7
        map[tv_num_8] = KeyEvent.KEYCODE_NUMPAD_8
        map[tv_num_9] = KeyEvent.KEYCODE_NUMPAD_9
        map[tv_num_0] = KeyEvent.KEYCODE_NUMPAD_0
        map[tv_num_dot] = KeyEvent.KEYCODE_NUMPAD_DOT
        map[tv_num_del] = KeyEvent.KEYCODE_DEL

        map.iterator().forEach { item ->
            item.key.setOnClickListener {
                (mView.parent as View).dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, item.value))
            }
        }

做好佈局後,在代碼中建立每個view與它所代表的鍵碼的對應關係,使用map持有它們,最後遍歷map集合,給每個view設置點擊事件,觸發點擊事件時,我們構造一個KeyEvent對象,然後找到當前view(即軟鍵盤view)的父view,調用其dispatchKeyEvent()方法,向其分發鍵盤事件,然後依靠安卓自身的事件處理機制,該事件就能被正確的傳遞給需要它的EditText。

利用系統自身的事件傳遞機制,去幫我們實現將KeyEvent轉化爲字符,輸入進EditText,是再好不過的,我們就不需要考慮直接操作EditText可能帶來的各種問題。

那麼,軟鍵盤view封裝好了,其他界面如何使用呢?

非常簡單:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="400dp"
        android:layout_height="350dp"
        android:orientation="vertical">
        <!-- 此處爲原本的業務View -->

    </LinearLayout>


    <.....KeypadView
        android:layout_width="263dp"
        android:layout_height="match_parent"
        android:layout_marginLeft="10dp"
        android:background="@drawable/res_best_white_button_default" />
</LinearLayout>

只需要一個LinearLayout 將原本的業務view和軟鍵盤view包括起來就OK了

好的,那麼正當我測試了幾個界面沒有問題的時候,我把這個軟鍵盤應用在另一個界面時,問題出現了:

 

二、問題:點擊軟鍵盤,沒有任何反應,輸入框沒有填入字符

就是這個界面,我沒有發現它與正常界面有何區別,於是只能嘗試通過底層源碼,來尋找問題原因

那麼我首先懷疑是因爲dispatchKeyEvent()方法沒有正確將按鍵事件傳遞給EditText,但是經過調試後,發現事件確實有傳遞過來

首先,當我們通過debug模式,查看安卓源碼時,記得打開源文件後,在右上角選擇和你運行的機器對應的安卓sdk版本,比如我的真機,安卓版本是5.1.1,那麼此處我要選擇查看api 22 版本的源代碼。這樣就不會出現斷點亂跳,無法定位代碼的問題。

斷點之後,通過代碼邏輯可知,返回0表示沒有處理/消耗此事件,那麼問題很有可能出現在這個doKeyDown()方法內

在doKeyDown()方法內部,我發現了可疑代碼,這段代碼判斷了,當KeyEvent的按鍵動作是按下時,使用了類似於數據庫事務操作的方式,對其進行編輯

並返回一個布爾值,如果爲true,則返回到外部:1,表示該keyEvent被處理/消耗。

繼續深入該方法,發現KeyListener是一個接口,擁有好幾個實現類

鼠標停留在變量上,可以看到此時,實現類是TextKeyListener

跟蹤TextKeyListener的onKeyDown方法,最終發現,它實際調用的是QwertyKeyListener的onKeyDown()方法

此時,我發現了一行關鍵的代碼,當我使用小鍵盤的1的code(KEYCODE_NUMPAD_1),和大鍵盤的1的code(KEYCODE_1),得到了截然不同的結果

接着看KeyEvent的getUnicodeChar()方法

    /**
     * Gets the Unicode character generated by the specified key and meta
     * key state combination.
     * <p>
     * Returns the Unicode character that the specified key would produce
     * when the specified meta bits (see {@link MetaKeyKeyListener})
     * were active.
     * </p><p>
     * Returns 0 if the key is not one that is used to type Unicode
     * characters.
     * </p><p>
     * If the return value has bit {@link KeyCharacterMap#COMBINING_ACCENT} set, the
     * key is a "dead key" that should be combined with another to
     * actually produce a character -- see {@link KeyCharacterMap#getDeadChar} --
     * after masking with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}.
     * </p>
     *
     * @param metaState The meta key modifier state.
     * @return The associated character or combining accent, or 0 if none.
     */
    public int getUnicodeChar(int metaState) {
        return getKeyCharacterMap().get(mKeyCode, metaState);
    }

原因:傳入小鍵盤鍵碼,和大鍵盤鍵碼,得到的結果不一致

根據註釋,可以得知該方法即爲將按鍵事件,轉爲字符的核心方法,根據keyCode和鍵盤控制鍵(如Ctrl,Shift,NumLock等),獲得一個字符

當我們傳入小鍵盤的鍵碼時,無法正確獲得對應字符,當傳入大鍵盤的鍵碼時,卻可以獲得字符,這就是問題的原因。

那麼,下一個問題來了,

 

三、爲什麼在前面經過測試的其他界面中,軟鍵盤卻又可以正常錄入字符呢?

那麼,我們找到可以正常使用軟鍵盤的界面,再重複一遍以上過程,

發現這次,代碼沒有進入TextKeyListener和QwertyKeyListener,而是進入了另一個實現類,NumberKeyListener的onKeyDown方法

可以發現,此處的區別是,它並沒有用上面的getUnicodeChar()方法去轉換字符,而是通過自己的lookup()方法,轉換字符

原因:使用了不同的KeyListener實現類

那麼,結合類名和現象,我們可以推測出:

TextKeyListener和QwertyKeyListener,他們的字符轉換功能,不支持小鍵盤的鍵碼(即虛擬鍵盤失效,有問題的界面)

NumberKeyListener的字符轉換功能,支持小鍵盤鍵碼(即前面測試過,功能ok的界面)

找到原因後,修復的辦法也顯而易見,只要讓我們存在問題的界面內的EditText,使用NumberKeyListener做字符轉換即可

使用EditText的 setInputType(EditorInfo.TYPE_CLASS_NUMBER)

將其設置爲只能輸入數字即可

 

但是,突然又想到,這個界面是需要支持輸入商品編碼的,而我們的部分商品編碼是以字母Z開頭的,所以設置爲只能輸入數字,將會影響業務邏輯

此時,看到了最開始我們發射事件的代碼,

        map[tv_num_1] = KeyEvent.KEYCODE_NUMPAD_1
        map[tv_num_2] = KeyEvent.KEYCODE_NUMPAD_2
        map[tv_num_3] = KeyEvent.KEYCODE_NUMPAD_3
        map[tv_num_4] = KeyEvent.KEYCODE_NUMPAD_4
        map[tv_num_5] = KeyEvent.KEYCODE_NUMPAD_5
        map[tv_num_6] = KeyEvent.KEYCODE_NUMPAD_6
        map[tv_num_7] = KeyEvent.KEYCODE_NUMPAD_7
        map[tv_num_8] = KeyEvent.KEYCODE_NUMPAD_8
        map[tv_num_9] = KeyEvent.KEYCODE_NUMPAD_9
        map[tv_num_0] = KeyEvent.KEYCODE_NUMPAD_0
        map[tv_num_dot] = KeyEvent.KEYCODE_NUMPAD_DOT

裏面的鍵碼全部是用的小鍵盤鍵碼,直接把它們都改爲大鍵盤鍵碼,問題不就都迎刃而解了嗎

改來改去,最後發現問題的原因只是最初發射事件的源頭。。

哈哈,最後針對安卓中的鍵盤事件處理機制,做一下總結

安卓KeyEvent的處理機制總結:

  1. 如果需要做虛擬鍵盤,模擬鍵盤輸入時,可以藉助安卓本身的KeyEvent機制,自己構造KeyEvent對象,使用dispatchKeyEvent()方法將事件分發給父View,剩下的都可以交給系統自行處理~
  2. 如果出現事件無效的問題,檢查自己構造的鍵碼是否正確,切換大/小鍵盤鍵碼進行嘗試
  3. 將KeyEvent事件轉換爲字符輸入的工作,是由KeyListener的實現類完成的
  4. KeyListener有很多實現類,每個實現類的職責不同,我們可以通過EditText的setInputType()方法,選擇不同的實現類,來實現最終目的

 

 

 

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