话说,这个自定义软键盘还是蛮多的,比如说一些密码键盘,在不知道KeyboardView的情况下,通常的做法就是屏蔽系统输入法,然后点击EditText的时候,唤起一个底部弹出,然后点那个输入哪个,但是删除啥的自己处理。
至于为什么会令我知道这个类,那就得说到一篇blog了《Android输入法流程详解》,看了又等于没有看。大致和这种情况差不多:
本着不可能只有一种实现思路的原则,我搜索了下Android自定义软键盘结果便检索了一大片关于KeyBoard的相关blog,emmmm? 那我也写一份,那就开整。
正文
结合通过dialog去实现实现一个密码键盘的经验和上面blog一知半解的想法,那么所谓的软键盘一定的有通过某种方式和Editext进行交互,然后又一个显示的值和一个真是传入到输入框的值,通过这种逻辑在KeyboardView的源码里面就找到了实现对应诉求的控制类。Keyboard,但是KeyboardView标记为过期是怎么回事?通过官网上的信息:
此类在 API 级别 29 中已弃用。 此类已弃用,因为这只是一个方便的 UI 小部件类,应用程序开发人员可以在现有公共 API 之上重新实现。如果您已经依赖于此类,请考虑将 AOSP 的实现复制到您的项目中或自己重新实现类似的小部件。KeyboardView和Keyboard 都被标记过期了。
那么了解 KeyboardView 定义软键盘这个view的原理就很重要了。但是呢?基于一个原则:
想要理解一个框架,最先做的是看他解决了什么
所以,我们第一件事情是如何使用他。
简单使用
因为KeyboardView继承于view,也不是abstract。那么我们就直接使用就好,至于封装一下,代码是不是好看这个问题,先不考虑,既然是view,那就可以可以当成view那么添加到xml里面。
定义界面xml
我们界面也很简单,就一个输入框,一个keyboardView.
ini
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<android.inputmethodservice.KeyboardView
android:id="@+id/keyBoard"
android:layout_alignParentBottom="true"
android:focusable="true"
android:visibility="gone"
android:focusableInTouchMode="true"
android:keyTextColor="@color/white"
android:keyBackground="@color/black"
android:background="#ff0077"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
很单纯,为了让感觉上是一个软键盘,我们先把它隐藏起来。
屏蔽系统输入法
我们知道,editext 获取焦点的时候,就会默认唤起系统输入法。所以这个并不是我们想要的,那么就直接屏蔽掉,代码也很简单:
ini
binding.edit.showSoftInputOnFocus=false
我网络上检索了很多editext不唤起系统输入法的方法,就这句代码管用。单独设置这句代码没有生效。
scss
activity?.window?.setSoftInputMode(SOFT_INPUT_STATE_HIDDEN)
windowManager.layoutParams.softInputMode 用于指定窗口的软键盘模式
- soft_input_adjust_resize 当软键盘弹起的时候,系统会将窗口的布局调整为适合软键盘显示的大小,这可能会导致某些视图的位置喝大小发生变化。
- soft_input_adjust_pan,当软键盘弹起的时候,系统会将窗口的可见区域向上移动,以露出被软键盘遮挡的部分视图,这种模式适合与需要用户再软键盘弹出时仍然能够看到窗口的部分内容。
- soft_input_ajust_relayout,系统会尝试重新排列创建的视图,以便于再软键盘弹出时不会遮挡任何内容,这可能会导致某些视图的位置发生变化,单不会改变其大小。
- soft_input_adjust_nothing 系统不会对窗口布局进行调整,软键盘的弹出不会影响窗口的视图
- soft_input_state_always_visible 软键盘始终可见,即使在窗口中没有输入焦点的也是如此。
- soft_input_state_always_hidden 软键盘始终隐藏,即使在窗口中获得输入的焦点的时候也是如此。
// todo 这不符合文档描述,这个后续再看吧。
获取到keyboard 对象
我们直接定义xml,毕竟这个支持这么定义。至于说像银行APP动态的按键,这个也支持,先怎么简单怎么来。
ini
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:horizontalGap="2.5%p"
android:keyWidth="30%p"
android:keyHeight="@dimen/key_num_height"
android:verticalGap="@dimen/key_vertical_gap">
<Row>
<Key
android:codes="49"
android:keyLabel="1" />
<Key
android:codes="50"
android:keyLabel="2" />
<Key
android:codes="51"
android:keyLabel="3" />
</Row>
<Row>
<Key
android:codes="52"
android:keyLabel="4" />
<Key
android:codes="53"
android:keyLabel="5" />
<Key
android:keyOutputText="靓仔啊"
android:codes="-5"
android:isRepeatable="true" />
</Row>
</Keyboard>
为了节约空间xml没有贴完,key元素通过一些属性来定义每个按键:
- codes 代表按键对应的输出值,对应的是onKey 回调的primaryCode
- keyLabel:代表按键的显示的文本内容
- keyIcon 按键的显示图标,如果有这个值,就不显示keyLable
- keyWidth 按键的宽度。可以是px,dp等,比如说%p表示相对于父容器。
- keyHeight 和 keyWidth 约束类似
- horizontalGap 按键水平方向的间隙
- isSticky 是否是两种状态,比如大小写切换。
- isModifier 是否是功能按钮
- kyeOutPutText 自定按键输入的文本内容,取值为字符串
- isRepeatable 按键是否可重复
- keyEdgeFlags 指定按钮的对其指令
基于这些可以自定义,当然这里面很多,我Demo并没有用上。最后是生成这个对象了:
scss
Keyboard(requireContext(),R.xml.symbols)
至于为啥codes是从49开始的,而不是0开始的:因为
Android自定义键盘里面的codes是从49开始是因为49是键盘上的"1"键对应的ASCII码值。键盘上的按键按照它们在键盘上的物理位置进行排序,从左上角的第一行第一列开始,向右移动一列向下移动一行,然后再从左上角的第一行第一列开始排序。因此,键盘上第一行第一列的"1"键对应的ASCII码值是49,第二行第一列的"2"键对应的ASCII码值是50,以此类推。
所以说,我们可以通过ASCII 直接获取到对应的值,比如说在onKey中:
ini
val correspondingCharacter = primaryCode.toChar() // 将 ASCII 码值转换为字符
val correspondingString = correspondingCharacter.toString() // 将字符转换为字符串
按键的监听KeyboardView.OnKeyboardActionListener
这个最好是写一个静态类,把editext 传递进去,要不然一个软键盘一个是输入框,我们又没有写service 咋关联。
kotlin
class OnKeyboardActionListener(private val edit: EditText): KeyboardView.OnKeyboardActionListener{
override fun onPress(primaryCode: Int) {
}
override fun onRelease(primaryCode: Int) {
}
override fun onKey(primaryCode: Int, keyCodes: IntArray) {
val editable= edit.text
val start= edit.selectionStart
val correspondingCharacter = primaryCode.toChar() // 将 ASCII 码值转换为字符
val correspondingString = correspondingCharacter.toString() // 将字符转换为字符串
editable.insert(start,correspondingString)
}
override fun onText(text: CharSequence?) {
val editable= edit.text
val start= edit.selectionStart
editable.insert(start,text)
}
override fun swipeLeft() {
}
override fun swipeRight() {
}
override fun swipeDown() {
}
override fun swipeUp() {
}
}
- onKey 当用户按下或释放一个键的时候调用,primaryCode 是按下键的ascii值。keycodes 是按键数组,只是按了一个键只有下标为0是按键的值,其他的是-1,逻辑上只要手机支持的触摸点够多,你可以按所有键。
- onPress 用户按下某个键的时候调用
- onRelease 当用户释放键盘上的某个键的时候调用。
- onText 当用户在键盘上输入文本的时候调用,text是用户输入的文本内容。上面xml里面codes=-5 那个按钮,按钮就会调用这个方法,当调用了这个方法onKey 就不再调用了,但是onPress和onRelease 还是会调用。
- swipeLeft 左滑,这些滑动频率得比较大才会触发。
- swipeRight 右滑
- swipeDown 下滑
- swipeUp 上滑
整合绑定
代码很简单。通过设置editext的touch事件,然后把软键盘view 显示出来就OK,隐藏?写Demo要什么隐藏。
ini
binding.keyBoard.apply {
keyboard=Keyboard(requireContext(),R.xml.symbols)
isEnabled=true
isPreviewEnabled=false
setOnKeyboardActionListener(OnKeyboardActionListener(binding.edit))
}
//activity?.window?.setSoftInputMode(SOFT_INPUT_STATE_HIDDEN)
// 不唤起系统输入法
binding.edit.showSoftInputOnFocus=false
binding.edit.setOnTouchListener { v, event ->
binding.keyBoard.visibility=View.VISIBLE
return@setOnTouchListener false
}
当我们把代码拼接运行起来的时候,就发现整个流程是通的。
总结
这个简单使用上,我们可以发现这种输入法和自己写一个dialog然后取持有editext 没有任何区别,主要是Android 源码封装的更加规范。demo源码地址 要说知识点,感觉很少。
- binding.edit.showSoftInputOnFocus=false 可以屏蔽系统输入法,同时又有焦点
- 使用KeyboardView 定义特定键盘比较快,当然把KeyboardView包到一个dialog里面更像输入法。
- windowManager.layoutParams.softInputMode 有哪些模式
- 49是键盘上的"1"键对应的ASCII码
- 操作editable就可以直接更新editext 中的内容
- 如果仔细读代码的话,还有如何解析xml 文件,Android工程师如何画这种多列表的自定义view啥的。