欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

程序员文章站 2022-07-05 09:10:27
一、实现一个可以模拟输入的软键盘一开始,我们的需求是在用户经常使用的部分界面中,增加虚拟软键盘,减少用户对于外接键盘的依赖如图,在整单改价界面右侧增加了方便用户快捷输入的软键盘,用户不需要使用外接键盘,即可完成常见的商品改价等操作。那么这个代码逻辑实现起来比较简单,因为业务中有许多类似界面需要使用该软键盘功能,所以我们将它单独封装为一个View: mView = View.inflate(context, R.layout.res_keypad_view, this......

目录

一、实现一个可以模拟输入的软键盘

二、问题:点击软键盘,没有任何反应,输入框没有填入字符

原因:传入小键盘键码,和大键盘键码,得到的结果不一致

三、为什么在前面经过测试的其他界面中,软键盘却又可以正常录入字符呢?

原因:使用了不同的KeyListener实现类

安卓KeyEvent的处理机制总结:


一、实现一个可以模拟输入的软键盘

一开始,我们的需求是在用户经常使用的部分界面中,增加虚拟软键盘,减少用户对于外接键盘的依赖

安卓中的虚拟键盘实现,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了

好的,那么正当我测试了几个界面没有问题的时候,我把这个软键盘应用在另一个界面时,问题出现了:

 

二、问题:点击软键盘,没有任何反应,输入框没有填入字符

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

就是这个界面,我没有发现它与正常界面有何区别,于是只能尝试通过底层源码,来寻找问题原因

那么我首先怀疑是因为dispatchKeyEvent()方法没有正确将按键事件传递给EditText,但是经过调试后,发现事件确实有传递过来

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

首先,当我们通过debug模式,查看安卓源码时,记得打开源文件后,在右上角选择和你运行的机器对应的安卓sdk版本,比如我的真机,安卓版本是5.1.1,那么此处我要选择查看api 22 版本的源代码。这样就不会出现断点乱跳,无法定位代码的问题。

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

断点之后,通过代码逻辑可知,返回0表示没有处理/消耗此事件,那么问题很有可能出现在这个doKeyDown()方法内

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

在doKeyDown()方法内部,我发现了可疑代码,这段代码判断了,当KeyEvent的按键动作是按下时,使用了类似于数据库事务操作的方式,对其进行编辑

并返回一个布尔值,如果为true,则返回到外部:1,表示该keyEvent被处理/消耗。

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

继续深入该方法,发现KeyListener是一个接口,拥有好几个实现类

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

鼠标停留在变量上,可以看到此时,实现类是TextKeyListener

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

跟踪TextKeyListener的onKeyDown方法,最终发现,它实际调用的是QwertyKeyListener的onKeyDown()方法

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

此时,我发现了一行关键的代码,当我使用小键盘的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等),获得一个字符

当我们传入小键盘的键码时,无法正确获得对应字符,当传入大键盘的键码时,却可以获得字符,这就是问题的原因。

那么,下一个问题来了,

 

三、为什么在前面经过测试的其他界面中,软键盘却又可以正常录入字符呢?

那么,我们找到可以正常使用软键盘的界面,再重复一遍以上过程,

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

发现这次,代码没有进入TextKeyListener和QwertyKeyListener,而是进入了另一个实现类,NumberKeyListener的onKeyDown方法

安卓中的虚拟键盘实现,KeyEvent的事件分发、处理机制

可以发现,此处的区别是,它并没有用上面的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()方法,选择不同的实现类,来实现最终目的

 

 

 

本文地址:https://blog.csdn.net/kaixuan_dashen/article/details/112261931