前言
这篇文章是介绍AndroID中自定义键盘的一些套路,通过定义一个数字键盘为例,本篇的文章语言是基于Kotlin实现的,如果还没有用或者不熟悉该语言的同学,可以自己补习,我之前也写过入门文章。
效果图
github:源码传送门
本地下载:源码传送门
加载键盘存储键属性的XML描述
我们下面的介绍都是依靠上图的实现来展开的,首先是软键盘的布局,我们需要我们的res/xml目录下创建一个xml文件,根节点就是Keyboard,然后就是键盘的每一行Row,每一行中可以指定每一列,也就是具体的键Key,代码实现
<?xml version="1.0" enCoding="utf-8"?><!--isRepeatable:长按时是否重复这个 *** 作--><Keyboard xmlns:androID="http://schemas.androID.com/apk/res/androID" androID:horizontalGap="1px" androID:keyHeight="7%p" androID:keyWIDth="33.33%p" androID:verticalGap="1px"> <Row androID:keyHeight="6%p"> <Key androID:codes="-4" androID:keyIcon="@drawable/hIDden" androID:keyWIDth="100%" /> </Row> <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: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="46" androID:keyLabel="." /> <Key androID:codes="48" androID:keyLabel="0" /> <Key androID:codes="-5" androID:isRepeatable="true" androID:keyIcon="@drawable/delete" /> </Row></Keyboard>
在Keyboard节点属性中,我们通过horizontalGap设置水平的间距,通过verticalGap设置垂直的间距,通过keyWIDth设置每一个key的宽度,通过keyHeight设置。需要注意的地点是如果Keyboard ,Row和Key都可以指定宽高。通常我们可以指定在Keyboard 中设置每一个键的宽高就可以了。当然如果对特定行的宽高要有所调整,可以在Row 或者key上设置,例如我们示例图中展示的最上面的一行,它的宽度比其它行都低了一点,则我们在第一行设置了属性androID:keyHeight="6%p"
在每一个key中有下面常用属性
1、androID:codes
官网介绍是说这个是该键的unicode 值或者逗号分隔值,当然我们也可以设置成我们想要的值,在源码中提供了几个特定的值
//就不解释了,通过名字应该看得出来 public static final int KEYCODE_SHIFT = -1; public static final int KEYCODE_MODE_CHANGE = -2; public static final int KEYCODE_CANCEL = -3; public static final int KEYCODE_DONE = -4; public static final int KEYCODE_DELETE = -5; public static final int KEYCODE_ALT = -6;
2、androID:keyOutputText
设置该值后,当点击key时回调onText(text: CharSequence?)
会执行,参数就是我们设置的值。
3、androID:keyIcon
设置key上显示的icon
4、androID:keyLabel
键上显示的值
5、androID:isRepeatable
当长按时是否重复该键设置的 *** 作,例如我们删除键可以设置此属性。
6、androID:keyEdgeFlags
该属性有两个值,分别是left,right,用与指定显示在最左还是最右,一般不用此属性。默认从左到右排列。
还有其它属性,不在介绍,可以自己去查阅api
自定义KeyboardVIEw
该类是用来渲染虚拟键盘的类,类中有一个接口OnKeyboardActionListener能检测按键和触摸动作,我们要自定义虚拟键盘,只需要继承该类并实现该监听接口即可,当然我这里并没有实现接口,我单独创建了一个工具类,用于将自定义键盘VIEw和EditText关联,并设置接口监听,这些稍后介绍到再说,我们最主要关注的就是onDraw方法,它可以让我们自定义键盘的绘制,随心所欲的画我们想要的东西。当然,我们也可以不做任何实现,它默认的有一种绘制。
class CustomKeyboardVIEw : KeyboardVIEw { private var mKeyBoard: Keyboard? = null constructor(context: Context,attrs: AttributeSet) : this(context,attrs,0) {} constructor(context: Context,attrs: AttributeSet,defStyle: Int) : super(context,defStyle) { // } overrIDe fun onDraw(canvas: Canvas) { super.onDraw(canvas) mKeyBoard = this.keyboard var keys: MutableList<Keyboard.Key>? = null if (mKeyBoard != null) { keys = mKeyBoard!!.keys } if (keys != null) { for (key in keys) { //可以自定义自己的绘制(例如某个按钮绘制背景图片和文字,亦或者更改某个按钮颜色等) if (key.codes[0] == -111) {//过滤指定某个键自定义绘制 } } } }}
在上面的onDraw方法中,我们通过this.keyboard
(即java的getKeyboard方法,是KeyboardVIEw 中的方法)获取Keyboard对象,并通过mKeyBoard!!.keys
获取键盘的Key对象,即每一个键对象,如果我们想自定义绘制,就可以自己实现绘制,当然也可以针对个人键绘制,例如键上字体颜色,背景等。例如我们针对Key的code是 -111的自定义一些绘制 *** 作。
if (key.codes[0] == -111) {//过滤指定某个键自定义绘制 //绘制后,原来xml中的keyLabel以及keyIcon会被覆盖,如需显示文字 //需要自己重新绘制,要后绘制文字,否则文字不显示 drawBackground(R.drawable.bg_keyboardvIEw1,canvas,key) drawTextOrIcon(canvas,key) }
背景selector
<selector xmlns:androID="http://schemas.androID.com/apk/res/androID"> <item androID:drawable="@color/btnpressed" androID:state_pressed="true"/> <item androID:drawable="@color/btnnormal"/></selector>
需要注意的是需要先绘制背景,再绘制文字或icon,否则文字或者icon就看不到了,相信你肯定知道为啥,真不知道的话那....
//绘制背景 fun drawBackground(drawableID: Int,canvas: Canvas,key: Keyboard.Key) { var drawable = resources.getDrawable(drawableID) var drawableState: IntArray = key.currentDrawableState if (key.codes[0] != 0) { drawable.state=drawableState } drawable.bounds = Rect(key.x,key.y,key.x + key.wIDth,key.height + key.y) drawable.draw(canvas) }
绘制背景前先通过key.currentDrawableState
(java的getCurrentDrawableState() 方法,后面不在提了)获取当前的状态,然后设置到drawable,然后通过Rect指定绘制的区域。Rect参数分别是左上右下。key.x,key.对应的就是该key的左上角的坐标,则left=key.x
, top=key.y
,right=key.x+key.wIDth
,bottom=key.y+key.height
然后调用 drawable.draw(canvas)
开始绘制。
绘制完成背景之后,我们开始绘制文字或者icon。
//绘制文字或图标 fun drawTextOrIcon(canvas: Canvas,key: Keyboard.Key) { var bounds = Rect() var paint = Paint() paint.color = color.WHITE paint.isAntiAlias = true paint.textAlign = Paint.Align.CENTER paint.typeface = Typeface.DEFAulT if (key.label != null) { var label = key.label.toString() //为了将字体大小与默认绘制的Label字体大小相同,需要反射获取默认大小。然后在此处设置文字大小 //还有一种取巧的方法在布局文件keyboardvIEw中设置keyTextSize,labelTextSize var fIEld = KeyboardVIEw::class.java.getDeclaredFIEld("mLabelTextSize") fIEld.isAccessible = true var labelTextSize = fIEld.get(this) as Int paint.textSize = labelTextSize.tofloat() paint.getTextBounds(label,label.length,bounds) canvas.drawText(label,(key.x + key.wIDth / 2).tofloat(),(key.y + key.height / 2 + bounds.height() / 2).tofloat(),paint) } else if (key.icon != null) { key.icon.bounds = Rect(key.x + (key.wIDth - key.icon.intrinsicWIDth) / 2,key.y + (key.height - key.icon.intrinsicHeight) / 2,key.x + (key.wIDth - key.icon.intrinsicWIDth) / 2 + key.icon.intrinsicWIDth,key.y + (key.height - key.icon.intrinsicHeight) / 2 + key.icon.intrinsicHeight) key.icon.draw(canvas) } }
通过上面的代码,我们做了下判断如果有label的时候就绘制文字,如果没有但是有icon就绘制icon,否则不做处理。在这里可以指定绘制文字的大小,颜色等。需要注意的一点是文字大小,为了和显示的其他默认绘制key的大小相同,需要获取KeyboardVIEw中的mLabelTextSize或者mKeyTextSize,因为该变量没有提供暴露方法,所以需要我们反射 *** 作。当然还有一种取巧的方法,我们可以在xml中指定字体大小,在此设置成相同大小。对于坐标区域的计算上面已经做了分析。
布局使用
<?xml version="1.0" enCoding="utf-8"?><!--background:整个键盘的背景色keyBackground :设置键的背景keyPrevIEwHeight:预览高度keyPrevIEwLayout :设置预览布局keyPrevIEwOffset :设置反馈的垂直偏移量keyTextcolor :设置key标签文字颜色keyTextSize:设置key标签字体大小labelTextSize:设置带文本和图标的键上个的文本的小大--><com.code4androID.keyboard.CustomKeyboardVIEw xmlns:androID="http://schemas.androID.com/apk/res/androID" androID:ID="@+ID/keyboard_vIEw" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:background="@color/keyborad_line_color" androID:focusable="true" androID:focusableIntouchMode="true" androID:keyBackground="@drawable/bg_keyboardvIEw" androID:keyPrevIEwHeight="35dp" androID:keyPrevIEwLayout="@layout/keyboard_key_prevIEw" androID:keyPrevIEwOffset="0dp" androID:keyTextcolor="#8a8a8a" androID:keyTextSize="18sp" androID:labelTextSize="18sp" androID:paddingtop="0dp" androID:shadowcolor="#fff" androID:shadowRadius="0.0" />
我们创建了自定义的VIEw之后,需要再创建上面layout供加载。keyBackground属性是设置Key的背景,一般我们可以设置一个selected选择器。keyPrevIEwHeight设置预览的高度,即我们点击时会有一个提示效果。keyPrevIEwLayout是我们预览的布局,它需要是一个TextVIEw 。keyPrevIEwOffset是预览的偏移量,keyTextcolor设置key字体颜色,shadowRadius我们一般设置为0,它表示字体的阴影,如果不设置0.看起来回模糊。
创建工具类
在工具类中创建了两个构造方法
constructor(activity: Activity) : this(activity,true,false) /** * @param activity * @param isRandom 是否时随机键盘 * @param mIsDecimal 是否支持小数输入 */ constructor(activity: Activity,isRandom: Boolean,isDecimal: Boolean) { mActivity = activity mIsRandom = isRandom mIsDecimal = isDecimal mKeyboard = Keyboard(mActivity,R.xml.keyboard) addVIEwToRoot() }//加载自定义的键盘layout private fun addVIEwToRoot() { mKeyBoardVIEwContainer = mActivity.layoutInflater.inflate(R.layout.keyboardvIEw,null) //var frameLayout: FrameLayout = mActivity.window.decorVIEw as FrameLayout//不要直接往DecorVIEw(状态栏,内容,导航栏)中addVIEw,如使用这个则最后显示布局不全(一部分内容在导航栏区域) var frameLayout: FrameLayout = mActivity.window.decorVIEw.find(androID.R.ID.content) var lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.WRAP_CONTENT) lp.gravity = Gravity.BottOM frameLayout.addVIEw(mKeyBoardVIEwContainer,lp) mKeyBoardVIEw = mKeyBoardVIEwContainer.find(R.ID.keyboard_vIEw) }
在构造方法中初始化Keyboard,以及布局文件,在代码中我们看到我们获取到DecorVIEw中ID为androID.R.ID.content
的布局,该布局是FrameLayout 布局,我们创建的布局都是放在这个布局中了,对这方面不理解的可以看看我之前写的文章深入分析setContentView。为了让我们自定义的键盘显示在最下面,设置Gravity为BottOM,然后通过frameLayout.addVIEw(mKeyBoardVIEwContainer,lp)
添加到FrameLayout 中。
除此之外,我们创建一个函数attachTo(EditText)将EditText与我们自定义的键盘绑定
fun attachTo(editText: EditText) { //如果editText与上次设置的是同一个对象,并且键盘已经正在在显示,不再执行后续 *** 作 if (mEditText != null && mEditText == editText && mKeyBoardVIEw.visibility == VIEw.VISIBLE) return mEditText = editText Log.e(TAG,"attachTo") //根据焦点及点击监听,来显示或者隐藏键盘 onFoucsChange() //隐藏系统键盘 hIDeSystemSoftKeyboard() //显示自定义键盘 showSoftKeyboard() } private fun onFoucsChange() { mEditText!!.setonFocuschangelistener { v,hasFocus -> Log.e(TAG,"onFoucsChange:$hasFocus" + v) //如果获取焦点,并且当前键盘没有显示,则显示,并执行动画 if (hasFocus && mKeyBoardVIEw.visibility != VIEw.VISIBLE) { mKeyBoardVIEw.visibility = VIEw.VISIBLE startAnimation(true) } else if (!hasFocus && mKeyBoardVIEw.visibility == VIEw.VISIBLE) { //如果当前时失去较大,并且当前在键盘正在显示,则隐藏 mKeyBoardVIEw.visibility = VIEw.GONE startAnimation(false) } } mEditText!!.setonClickListener { Log.e(TAG,"setonClickListener") //根据上面焦点的判断,如果已经获取到焦点,并且键盘隐藏。再次点击时, // 焦点改变函数不会回调,所以在此判断如果隐藏就显示 if (mKeyBoardVIEw.visibility == VIEw.GONE) { mKeyBoardVIEw.visibility = VIEw.VISIBLE startAnimation(true) } } } private fun hIDeSystemSoftKeyboard() { //11版本开始需要反射setShowSoftinputOnFocus方法设置false,来隐藏系统软键盘 if (Build.VERSION.SDK_INT > 10) { var clazz = EditText::class.java var setShowSoftinputOnFocus: Method? = null setShowSoftinputOnFocus = clazz.getmethod("setShowSoftinputOnFocus",Boolean::class.java) setShowSoftinputOnFocus.isAccessible = true setShowSoftinputOnFocus.invoke(mEditText,false) } else { mEditText!!.inputType = inputType.TYPE_NulL } var inputMethodManager = mActivity.applicationContext.inputMethodManager inputMethodManager.hIDeSoftinputFromWindow(mEditText!!.windowToken,0) }private fun showSoftKeyboard() { if (mIsRandom) { //生成随机键盘 generaterandomKey() } else { //有序键盘 mKeyBoardVIEw.keyboard = mKeyboard } mKeyBoardVIEw.isEnabled = true //设置预览,如果设置false,则就不现实预览效果 mKeyBoardVIEw.isPrevIEwEnabled = true //设置可见 mKeyBoardVIEw.visibility = VIEw.VISIBLE //指定键盘d出动画 startAnimation(true) //设置监听 mKeyBoardVIEw.setonKeyboardActionListener(mOnKeyboardActionListener()) } private fun generaterandomKey() { var keys = mKeyboard.keys var numberKeys = mutablelistof<Keyboard.Key>() //保存数字 var nums = mutablelistof<Int>() //0的ASCII码是48,之后顺序加1 for (key in keys) { //过滤数字键盘 if (key.label != null && "0123456789".contains(key.label)) { nums.add(Integer.parseInt(key.label.toString())) numberKeys.add(key) } } var random = Random() var changeKey = 0//更改numberKeys对应的数值 while (nums.size > 0) { var size = nums.size var randomNum = nums[random.nextInt(size)] var key = numberKeys[changeKey++] key.codes[0] = 48 + randomNum key.label = randomNum.toString() nums.remove(randomNum) } mKeyBoardVIEw.keyboard = mKeyboard }
具体的解释已在代码中体现。
设置键盘监听
在上面代码中我们看一句mKeyBoardVIEw.setonKeyboardActionListener(mOnKeyboardActionListener())
,它就是设置键盘的监听。OnKeyboardActionListener接口是KeyboardVIEw的内部类,我们在此设置监听可以指定在对应的回调种 *** 作EditText。该接口回调方法如下
1、swipeUp()
当用户快速将手指从下向上移动时调用
2、swipeDown 方法
当用户快速将手指从上向下移动时调用
3、swipeleft
当用户快速将手指从右向左移动时调用
4、swipeRight()
当用户快速将手指从左向右移动时调用
5、onPress(primaryCode: Int)
点击key时调用primaryCode时对应key的codes值
6、onRelease(primaryCode: Int)
释放key时调用
7、onKey(primaryCode: Int,keyCodes: IntArray?)
我选择在此对EditText的编辑,onPress之后调用的。
8、onText(text: CharSequence?)
设置keyOutputText时会会回调
具体实现
inner class mOnKeyboardActionListener : KeyboardVIEw.OnKeyboardActionListener { overrIDe fun swipeRight() { Log.e(TAG,"swipeRight") } overrIDe fun onPress(primaryCode: Int) { Log.e(TAG,"onPress") //添加震动效果 mActivity.applicationContext.vibrator.vibrate(50) ////指定隐藏(确定)删除不显示预览 mKeyBoardVIEw.isPrevIEwEnabled = !(primaryCode == Keyboard.KEYCODE_DONE || primaryCode == Keyboard.KEYCODE_DELETE) } overrIDe fun onRelease(primaryCode: Int) { Log.e(TAG,"onRelease") } overrIDe fun swipeleft() { Log.e(TAG,"swipeleft") } overrIDe fun swipeUp() { Log.e(TAG,"swipeUp") } overrIDe fun swipeDown() { Log.e(TAG,"swipeDown") } overrIDe fun onKey(primaryCode: Int,keyCodes: IntArray?) { Log.e(TAG,"onKey primaryCode:$primaryCode keyCodes:$keyCodes") if (mEditText == null) throw RuntimeException("The mEditText is null,Please call attachTo method") mEditText?.let { var editable: Editable = it.text var textString = editable.toString() //获取光标位置 var start = it.selectionStart when (primaryCode) { //如果是删除键,editable有值并且光标大于0(即光标之前有内容),则删除 Keyboard.KEYCODE_DELETE -> { if (!editable.isNullOrEmpty()) { if (start > 0) { editable.delete(start - 1,start) } else { } } else { } } Keyboard.KEYCODE_DONE -> { hIDeSoftKeyboard() mOnOkClick?.let { //点击确定时,写一个回调,如果你对有确定的需求 it.onOkClick() } } else -> { // 由于promaryCode是用的ASCII码,则直接转换字符即可,46是小数点 if (primaryCode != 46 ) { //如果点击的是数字,不是小数点,则直接写入EditText,由于我codes使用的是ASCII码, // 则可以直接转换为数字。当然可以你也可以获取label,或者根据你自己随便约定。 editable.insert(start,Character.toString(primaryCode.tochar())) } else { //如果点击的是逗号 if (mIsDecimal && primaryCode == 46) { if ("" == textString) { //如果点的是小数点,并且当前无内容,自动加0 editable.insert(start,"0.") } else if (!textString.contains(".")) { //当前内容不含有小数点,并且光标在第一个位置,依然加0 *** 作 if (start == 0) { editable.insert(start,"0.") } else { editable.insert(start,".") } } else { //如果是不允许小数输入,或者允许小数,但是已经有小数点,则不 *** 作 } } else { } } } } } } overrIDe fun onText(text: CharSequence?) { Log.e(TAG,"onText:" + text.toString()) } } fun hIDeSoftKeyboard(): Boolean { if (mEditText == null) return false var visibility = mKeyBoardVIEw.visibility if (visibility == VIEw.VISIBLE) { startAnimation(false) mKeyBoardVIEw.visibility = VIEw.GONE return true } return false } fun startAnimation(isIn: Boolean) { Log.e(TAG,"startAnimation") var anim: Animation if (isIn) { anim = AnimationUtils.loadAnimation(mActivity,R.anim.anim_bottom_in) } else { anim = AnimationUtils.loadAnimation(mActivity,R.anim.anim_bottom_out) } mKeyBoardVIEwContainer.startAnimation(anim) }
当点击的是KEYCODE_DONE 时,调用hIDeSoftKeyboard函数隐藏键盘,并执行隐藏动画,动画的xml文件就不在贴出了。
具体使用方式如下
keyboardUtli = KeyBoardUtil(this@KeyBoardDemoActivity) et_keyboard.setontouchListener { v,event -> keyboardUtli?.attachTo(et_keyboard) //设置是否可以输入小数 keyboardUtli?.mIsDecimal = true false } et_keyboard2.setontouchListener { v,event -> keyboardUtli?.attachTo(et_keyboard2) keyboardUtli?.mIsDecimal = false false }
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。
总结以上是内存溢出为你收集整理的Android开发之如何自定义数字键盘详解全部内容,希望文章能够帮你解决Android开发之如何自定义数字键盘详解所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)