Android 自定义键盘整理

前一段时间,由于业务需要,了解了一下关于自定义键盘的相关操作。

实现方式

主要是两种实现方式,我这里就以一个数字键盘来作为实现。
方式一效果图
方式一
方式二效果图
方式二

方式一

该方式是使用我们最原始的方式,自己画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)

总结

方式一 : 写法简单,适用于功能单一,样式简单的场景。

方式二 : 相对复杂一点,适用于功能倾向于系统键盘,长按出现悬浮框等系统键盘所特有功能(当然这种情况你也可以去自己实现。)


代码地址

猜你喜欢

转载自blog.csdn.net/lv_fq/article/details/81260713
今日推荐