安卓中的虚拟键盘实现,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()方法,选择不同的实现类,来实现最终目的

 

 

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页