前一段时间,由于业务需要,了解了一下关于自定义键盘的相关操作。
实现方式
主要是两种实现方式,我这里就以一个数字键盘来作为实现。
方式一效果图
方式二效果图
方式一
该方式是使用我们最原始的方式,自己画UI ,然后去实现监听事件,
这种方式我们就不过多废话,先来看一下自己画的 xml ,
<?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:background="@color/white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_60"
android:orientation="horizontal">
<TextView
android:id="@+id/kb_tv_1"
style="@style/keyboard_text_style"
android:text="1" />
<View style="@style/keyboard_ver_line" />
<TextView
android:id="@+id/kb_tv_2"
style="@style/keyboard_text_style"
android:text="2" />
<View style="@style/keyboard_ver_line" />
<TextView
android:id="@+id/kb_tv_3"
style="@style/keyboard_text_style"
android:text="3" />
</LinearLayout>
<View style="@style/keyboard_hor_line" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_60"
android:orientation="horizontal">
<TextView
android:id="@+id/kb_tv_4"
style="@style/keyboard_text_style"
android:text="4" />
<View style="@style/keyboard_ver_line" />
<TextView
android:id="@+id/kb_tv_5"
style="@style/keyboard_text_style"
android:text="5" />
<View style="@style/keyboard_ver_line" />
<TextView
android:id="@+id/kb_tv_6"
style="@style/keyboard_text_style"
android:text="6" />
</LinearLayout>
<View style="@style/keyboard_hor_line" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_60"
android:orientation="horizontal">
<TextView
android:id="@+id/kb_tv_7"
style="@style/keyboard_text_style"
android:text="7" />
<View style="@style/keyboard_ver_line" />
<TextView
android:id="@+id/kb_tv_8"
style="@style/keyboard_text_style"
android:text="8" />
<View style="@style/keyboard_ver_line" />
<TextView
android:id="@+id/kb_tv_9"
style="@style/keyboard_text_style"
android:text="9" />
</LinearLayout>
<View style="@style/keyboard_hor_line" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_60"
android:orientation="horizontal">
<TextView
style="@style/keyboard_text_style"
android:background="#f5f6f8" />
<View style="@style/keyboard_ver_line" />
<TextView
android:id="@+id/kb_tv_0"
style="@style/keyboard_text_style"
android:text="0" />
<View style="@style/keyboard_ver_line" />
<TextView
android:id="@+id/kb_tv_del"
style="@style/keyboard_text_style"
android:text="删除"
android:textSize="@dimen/sp_20" />
</LinearLayout>
</LinearLayout>
xml 中也就是我们常规的根据自己想要的布局去进行排版。下面是我们整合做为一个自定义 View 代码,无非就是对于我们 xml 中的布局进行设置点击事件,进行插入到输入框中:
class CusKeyboardView(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr), View.OnClickListener {
constructor(context: Context, attrs: AttributeSet? = null) : this(context, attrs, 0)
constructor(context: Context) : this(context, null)
private var editText: EditText? = null
fun bindView(editText: EditText) {
this.editText = editText
hideSystemSoftKeyboard(editText)
}
init {
View.inflate(context, R.layout.layout_keyboard, this)
kb_tv_0.setOnClickListener(this)
kb_tv_1.setOnClickListener(this)
kb_tv_2.setOnClickListener(this)
kb_tv_3.setOnClickListener(this)
kb_tv_4.setOnClickListener(this)
kb_tv_5.setOnClickListener(this)
kb_tv_6.setOnClickListener(this)
kb_tv_7.setOnClickListener(this)
kb_tv_8.setOnClickListener(this)
kb_tv_9.setOnClickListener(this)
kb_tv_del.setOnClickListener(this)
}
override fun onClick(v: View) {
when (v) {
kb_tv_0 -> {
insert("0")
}
kb_tv_1 -> {
insert("1")
}
kb_tv_2 -> {
insert("2")
}
kb_tv_3 -> {
insert("3")
}
kb_tv_4 -> {
insert("4")
}
kb_tv_5 -> {
insert("5")
}
kb_tv_6 -> {
insert("6")
}
kb_tv_7 -> {
insert("7")
}
kb_tv_8 -> {
insert("8")
}
kb_tv_9 -> {
insert("9")
}
kb_tv_del -> {
delete()
}
}
}
private fun insert(text: String) {
editText?.let {
it.editableText.insert(it.selectionStart, text)
}
}
private fun delete() {
editText?.let {
val start = it.selectionStart
if (start > 0) {
it.editableText.delete(start - 1, start)
}
}
}
}
这样我们第一种老土的自定义键盘方式就搞定了。当然,这种方式可就适用于自定义一下简单的数字键盘。如果再复杂一些的话,推荐还是使用以下第二种方式。
用法:
在布局文件中:
<xxx.CusKeyboardView
android:id="@+id/ck_bv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
在类中调用:
ck_bv.bindView(EditText)
方式二
第二种方式,我们这里就需要借助于系统的 KeyboardView 了。也就是用系统给我们提供的自定义键盘的方法。
首先,我们需要自定义一个 Keyboard xml 文件,存放在 res/xml (在 res 下创建一个 xml 目录) 目录下,还是一个数字键盘,我们来看一下 Keyboard 的xml文件内容:
<?xml version="1.0" encoding="utf-8"?>
<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
android:horizontalGap="@dimen/dp_2"
android:keyHeight="@dimen/dp_49"
android:keyWidth="33%p"
android:verticalGap="@dimen/dp_2">
<Row>
<Key
android:codes="49"
android:keyEdgeFlags="left"
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:codes="54"
android:keyLabel="6" />
</Row>
<Row>
<Key
android:codes="55"
android:keyLabel="7" />
<Key
android:codes="56"
android:keyLabel="8" />
<Key
android:codes="57"
android:keyLabel="9" />
</Row>
<Row>
<Key
android:codes="48"
android:horizontalGap="34%p"
android:keyLabel="0" />
<Key
android:codes="-5"
android:isRepeatable="true"
android:keyEdgeFlags="right"
android:keyLabel="删除" />
</Row>
</Keyboard>
Keyboard 官方介绍文档,也可以自己去查阅一下,
这里简单介绍一下关于 Keyboard.Key 的相关属性(更多属性请查阅 Keyboard 官方介绍文档)。文档如下:
Keyboard.Key 属性 | 介绍 |
---|---|
android:codes | 此键输出的unicode值或逗号分隔值。 |
android:horizontalGap | 键之间的默认水平间隙。 |
android:iconPreview | 弹出预览中显示的图标。 |
android:isModifier | 这是否是修改键,如Alt或Shift。 |
android:isRepeatable | 是否长按此键会使其重复。 |
android:isSticky | 这是否是切换键。 |
android:keyEdgeFlags | 关键边缘标志。 |
android:keyHeight | 键的默认高度,以像素为单位或显示宽度的百分比。 |
android:keyIcon | 要在键上显示的图标而不是标签。 |
android:keyLabel | 要在键上显示的标签。 |
android:keyOutputText | 按下此键时要输出的字符串。 |
android:keyWidth | 键的默认宽度,以像素为单位或显示宽度的百分比。 |
android:popupCharacters | 要在弹出键盘中显示的字符。 |
android:popupKeyboard | 任何弹出键盘的XML键盘布局。 |
另外,关于输入法中的每个字符所对应的codes 可以根据ASCII码对照表下方的ASCII可显示字符 codes 就是十进制值,keyLabel 对应的是 ‘图形’。
关于 KeyboardView 的相关属性配置只能通过在配置文件里面设置,我这里不想去写在配置文件中,所以 Copy 了系统的 KeyBoardView 属性进行作为自定义属性, 作为暴力反射去设置其相关参数。
下面来看一下相关定义代码 :
class SystemKeyboardView(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : LinearLayout(context, attrs, defStyleAttr) {
constructor(context: Context, attrs: AttributeSet? = null) : this(context, attrs, 0)
constructor(context: Context) : this(context, null)
private var editText: EditText? = null
fun bindView(editText: EditText) {
this.editText = editText
hideSystemSoftKeyboard(editText)
}
private val keyboardView by lazy {
KeyboardView(context, attrs).apply {
layoutParams = LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
// setBackgroundResource(R.drawable.select_s_e3e7ed_n_white)
}
}
private var mKeyBackground: Drawable
private var mLabelTextSize: Int = 0
private var mKeyTextSize: Int = 0
private var mKeyTextColor: Int = 0
private var mShadowRadius: Float = 0F
private var mShadowColor: Int = 0
init {
mKeyBackground = resources.getDrawable(R.drawable.select_s_e3e7ed_n_white) // 随便给了个默认值
val a = context.obtainStyledAttributes(attrs, R.styleable.SystemKeyboardView)
val n = a.indexCount
for (i in 0 until n) {
val attr = a.getIndex(i)
when (attr) {
R.styleable.SystemKeyboardView_keyBackground -> {
mKeyBackground = a.getDrawable(attr)
}
// R.styleable.SystemKeyboardView_verticalCorrection -> {
// mVerticalCorrection = a.getDimensionPixelOffset(attr, 0)
// }
// R.styleable.SystemKeyboardView_keyPreviewLayout -> {
// previewLayout = a.getResourceId(attr, 0)
// }
// R.styleable.SystemKeyboardView_keyPreviewOffset -> {
// mPreviewOffset = a.getDimensionPixelOffset(attr, 0)
// }
// R.styleable.SystemKeyboardView_keyPreviewHeight -> {
// mPreviewHeight = a.getDimensionPixelSize(attr, 80)
// }
R.styleable.SystemKeyboardView_keyTextSize -> {
mKeyTextSize = a.getDimensionPixelSize(attr, 18)
}
R.styleable.SystemKeyboardView_keyTextColor -> {
mKeyTextColor = a.getColor(attr, -0x1000000)
}
R.styleable.SystemKeyboardView_labelTextSize -> {
mLabelTextSize = a.getDimensionPixelSize(attr, 14)
}
// R.styleable.SystemKeyboardView_popupLayout -> {
// mPopupLayout = a.getResourceId(attr, 0)
// }
R.styleable.SystemKeyboardView_shadowColor -> {
mShadowColor = a.getColor(attr, 0)
}
R.styleable.SystemKeyboardView_shadowRadius -> {
mShadowRadius = a.getFloat(attr, 0f)
}
}
}
a.recycle()
addView(keyboardView)
// var mKeyBackground = getFieldValue(keyboardView, "mKeyBackground") as Drawable?
// mKeyBackground = resources.getDrawable(R.drawable.select_s_e3e7ed_n_white)
// 通过暴力反射 设置需要的属性
setFieldValue(keyboardView, "mKeyBackground", mKeyBackground)
setFieldValue(keyboardView, "mKeyTextSize", mKeyTextSize)
setFieldValue(keyboardView, "mKeyTextColor", mKeyTextColor)
setFieldValue(keyboardView, "mLabelTextSize", mLabelTextSize)
setFieldValue(keyboardView, "mShadowColor", mShadowColor)
setFieldValue(keyboardView, "mShadowRadius", mShadowRadius)
keyboardView.keyboard = Keyboard(context, R.xml.key_board_number)
keyboardView.isPreviewEnabled = false
keyboardView.setOnKeyboardActionListener(object : KeyboardView.OnKeyboardActionListener {
override fun swipeRight() {
}
override fun onPress(primaryCode: Int) {
}
override fun onRelease(primaryCode: Int) {
}
override fun swipeLeft() {
}
override fun swipeUp() {
}
override fun swipeDown() {
}
override fun onText(text: CharSequence?) {
}
override fun onKey(primaryCode: Int, keyCodes: IntArray?) {
editText?.let {
val start = it.selectionStart
when (primaryCode) {
Keyboard.KEYCODE_DELETE -> {
if (start > 0) {
it.editableText.delete(start - 1, start)
} else {
}
}
else -> {
it.editableText.insert(start, Character.toString(primaryCode.toChar()))
}
}
}
}
})
}
fun setFieldValue(obj: Any?, fieldName: String, value: Any) {
if (obj == null || TextUtils.isEmpty(fieldName)) {
return
}
val clazz: Class<*> = obj.javaClass
while (clazz != Any::class.java) {
try {
val field = clazz.getDeclaredField(fieldName)
field.isAccessible = true
field.set(obj, value)
return
} catch (e: Exception) {
}
}
}
}
相关自定义属性(values/attrs.xml)这个是直接把系统定义的属性拿过来使用了:
<declare-styleable name="SystemKeyboardView">
<!-- Default KeyboardView style. -->
<attr name="keyboardViewStyle" format="reference" />
<!-- Image for the key. This image needs to be a StateListDrawable, with the following
possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
checkable+checked+pressed. -->
<attr name="keyBackground" format="reference" />
<!-- Size of the text for character keys. -->
<attr name="keyTextSize" format="dimension" />
<!-- Size of the text for custom keys with some text and no icon. -->
<attr name="labelTextSize" format="dimension" />
<!-- Color to use for the label in a key. -->
<attr name="keyTextColor" format="color" />
<!-- Layout resource for key press feedback.-->
<attr name="keyPreviewLayout" format="reference" />
<!-- Vertical offset of the key press feedback from the key. -->
<attr name="keyPreviewOffset" format="dimension" />
<!-- Height of the key press feedback popup. -->
<attr name="keyPreviewHeight" format="dimension" />
<!-- Amount to offset the touch Y coordinate by, for bias correction. -->
<attr name="verticalCorrection" format="dimension" />
<!-- Layout resource for popup keyboards. -->
<attr name="popupLayout" format="reference" />
<attr name="shadowColor" />
<attr name="shadowRadius" />
</declare-styleable>
用法:
在布局文件中:
<xxx.SystemKeyboardView
android:id="@+id/ck_kv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/color_9b"
app:keyBackground="@drawable/select_s_e3e7ed_n_white"
app:keyTextColor="@color/red"
app:keyTextSize="@dimen/sp_28"
app:labelTextSize="@dimen/sp_28" />
在类中调用:
ck_kv.bindView(EditText)
总结
方式一 : 写法简单,适用于功能单一,样式简单的场景。
方式二 : 相对复杂一点,适用于功能倾向于系统键盘,长按出现悬浮框等系统键盘所特有功能(当然这种情况你也可以去自己实现。)