用Canvas绘制一个数字键盘
Hello啊老铁们,这篇文章还是阐述自定义View相关的内容,用Canvas轻轻松松搞一个数字键盘,本身没什么难度,这种效果实现的方式也是多种多样,这篇只是其中的一种,要说本篇有什么特别之处,可能就是纯绘制,没有用到其它的任何资源,一个类就搞定了,文中不足之处,各位老铁多包含,多指正。
今天的内容大概如下:
1、效果展示
2、快速使用及属性介绍
3、具体代码实现
4、源文件地址及总结
一、效果展示
很常见的数字键盘,背景,颜色,文字大小,点击的事件等等,均已配置好,大家可以看第2项中相关介绍。
静态效果展示:
动态效果展示,录了一个gif,大家可以看下具体的触摸效果。
二、快速使用及属性介绍
鉴于本身就一个类,不值当去打一个远程的Maven,大家用的话可以直接下载,把文件复制到项目里即可,复制到项目中,就可以按照下面的步骤去使用。
引用
1、xml中引用,可以根据需要,设置宽高及相关属性
<KeyboardView
android:id="@+id/key_board_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
2、代码直接创建,然后追加到相关视图里即可
val keyboardView=KeyboardView(this)
方法及属性介绍
单个点击的触发监听:
keyboardView.setOnSingleClickListener {
//键盘点击的数字
}
获取最终的字符串点击监听,其实就是把你点击的数字拼接起来,一起输出,特别在密码使用的时候,省的你再自己拼接了,配合number_size属性和setNumberSize方法一起使用,默认是6个长度,可以根据需求,动态设置。
keyboardView.setOnNumClickListener {
//获取最终的点击数字字符串,如:123456,通过number_size属性或setNumberSize方法,设置最长字符
}
其它方法
方法 | 参数 | 概述 |
---|---|---|
hintLetter | 无参 | 隐藏字母 |
setBackGroundColor | int类型的颜色值 | 设置整体背景色 |
setRectBackGroundColor | int类型的颜色值 | 设置数字格子背景色 |
setTextColor | int类型的颜色值 | 设置文字颜色 |
setTextSize | Float | 设置数字大小 |
setNumberSize | int类型 | 设置按下的数字总长度 |
setRectHeight | Float | 设置数字键盘每格高度 |
setSpacing | Float | 设置数字键盘每格间隔距离 |
属性介绍
属性 | 类型 | 概述 |
---|---|---|
background_color | color | 背景颜色 |
rect_background_color | color | 数字格子背景色 |
down_background_color | color | 手指按下的背景颜色 |
text_color | color | 文字颜色 |
text_size | dimension | 文字大小 |
letter_text_size | dimension | 字母的文字大小 |
rect_height | dimension | 数字格子高度 |
rect_spacing | dimension | 格子间距 |
is_rect_letter | boolean | 是否显示字母 |
number_size | integer | 按下的数字总长度字符 |
三、具体代码实现
代码实现上其实也没有什么难的,主要就是用到了自定义View中的onDraw方法,简单的初始化,设置画笔,默认属性就不一一介绍了,直接讲述主要的绘制部分,我的实现思路如下,第一步,绘制方格,按照UI效果图,应该是12个方格,简图如下,需要注意的是,第10个是空的,也就是再绘制的时候,需要进行跳过,最后一个是一个删除的按钮,绘制的时候也需要跳过,直接绘制删除按钮即可。
1、关于方格的绘制
方格的宽度计算很简单,(手机的宽度-方格间距*4)/3即可,绘制方格,直接调用canvas的drawRoundRect方法,单纯的和数字一起绘制,直接遍历12个数即可,记住9的位置跳过,11的位置,绘制删除按钮。
mRectWidth = (width - mSpacing * 4) / 3
mPaint!!.strokeWidth = 10f
for (i in 0..11) {
//设置方格
val rect = RectF()
val iTemp = i / 3
val rectTop = mHeight * iTemp + mSpacing * (iTemp + 1f)
rect.top = rectTop
rect.bottom = rect.top + mHeight
var leftSpacing = (mSpacing * (i % 3f))
leftSpacing += mSpacing
rect.left = mRectWidth!! * (i % 3f) + leftSpacing
rect.right = rect.left + mRectWidth!!
//9的位置是空的,跳过不绘制
if (i == 9) {
continue
}
//11的位置,是删除按钮,直接绘制删除按钮
if (i == 11) {
drawDelete(canvas, rect.right, rect.top)
continue
}
mPaint!!.textSize = mTextSize
mPaint!!.style = Paint.Style.FILL
//按下的索引 和 方格的 索引一致,改变背景颜色
if (mDownPosition == (i + 1)) {
mPaint!!.color = mDownBackGroundColor
} else {
mPaint!!.color = mRectBackGroundColor
}
//绘制方格
canvas!!.drawRoundRect(rect, 10f, 10f, mPaint!!)
}
2、关于数字的绘制
没有字母显示的情况下,数字要绘制到中间的位置,有字母的情况下,数字应该往上偏移,让整体进行居中,通过方格的宽高和自身文字内容的宽高来计算显示的位置。
//绘制数字
mPaint!!.color = mTextColor
var keyWord = "${i + 1}"
//索引等于 10 从新赋值为 0
if (i == 10) {
keyWord = "0"
}
val rectWord = Rect()
mPaint!!.getTextBounds(keyWord, 0, keyWord.length, rectWord)
val wWord = rectWord.width()
val htWord = rectWord.height()
var yWord = rect.bottom - mHeight / 2 + (htWord / 2)
//上移
if (i != 0 && i != 10 && mIsShowLetter) {
yWord -= htWord / 3
}
canvas.drawText(
keyWord,
rect.right - mRectWidth!! / 2 - (wWord / 2),
yWord,
mPaint!!
)
3、关于字母的绘制
因为字母是和数字一起绘制的,所以需要对应的字母则向下偏移,否则不会达到整体居中的效果,具体的绘制如下,和数字的绘制类似,拿到方格的宽高,以及字母的宽高,进行计算横向和纵向位置。
//绘制字母
if ((i in 1..8) && mIsShowLetter) {
mPaint!!.textSize = mLetterTextSize
val s = mWordArray[i - 1]
val rectW = Rect()
mPaint!!.getTextBounds(s, 0, s.length, rectW)
val w = rectW.width()
val h = rectW.height()
canvas.drawText(
s,
rect.right - mRectWidth!! / 2 - (w / 2),
rect.bottom - mHeight / 2 + h * 2,
mPaint!!
)
}
4、关于删除按钮的绘制
删除按钮是纯线条的绘制,没有使用图片资源,不过大家可以使用图片资源,因为图片资源还是比较的靠谱。
/**
* AUTHOR:AbnerMing
* INTRODUCE:绘制删除按键,直接canvas自绘,不使用图片
*/
private fun drawDelete(canvas: Canvas?, right: Float, top: Float) {
val rWidth = 15
val lineWidth = 35
val x = right - mRectWidth!! / 2 - (rWidth + lineWidth) / 4
val y = top + mHeight / 2
val path = Path()
path.moveTo(x - rWidth, y)
path.lineTo(x, y - rWidth)
path.lineTo(x + lineWidth, y - rWidth)
path.lineTo(x + lineWidth, y + rWidth)
path.lineTo(x, y + rWidth)
path.lineTo(x - rWidth, y)
path.close()
mPaint!!.strokeWidth = 2f
mPaint!!.style = Paint.Style.STROKE
mPaint!!.color = mTextColor
canvas!!.drawPath(path, mPaint!!)
//绘制小×号
mPaint!!.style = Paint.Style.FILL
mPaint!!.textSize = 30f
val content = "×"
val rectWord = Rect()
mPaint!!.getTextBounds(content, 0, content.length, rectWord)
val wWord = rectWord.width()
val htWord = rectWord.height()
canvas.drawText(
content,
right - mRectWidth!! / 2 - wWord / 2 + 3,
y + htWord / 3 * 2 + 2,
mPaint!!
)
}
5、按下效果的处理
按下的效果处理,重写onTouchEvent方法,然后在down事件里通过,手指触摸的XY坐标,判断当前触摸的是那个方格,记录下索引,并使用invalidate进行刷新View,在onDraw里进行改变画笔的颜色即可。
根据XY坐标,返回触摸的位置
/**
* AUTHOR:AbnerMing
* INTRODUCE:返回触摸的位置
*/
private fun getTouch(upX: Float, upY: Float): Int {
var position = -2
for (i in 0..11) {
val iTemp = i / 3
val rectTop = mHeight * iTemp + mSpacing * (iTemp + 1f)
val top = rectTop
val bottom = top + mHeight
var leftSpacing = (mSpacing * (i % 3f))
leftSpacing += 10f
val left = mRectWidth!! * (i % 3f) + leftSpacing
val right = left + mRectWidth!!
if (upX > left && upX < right && upY > top && upY < bottom) {
position = i + 1
//位置11默认为 数字 0
if (position == 11) {
position = 0
}
//位置12 数字为 -1 意为删除
if (position == 12) {
position = -1
}
}
}
return position
}
在onDraw里进行改变画笔的颜色。
//按下的索引 和 方格的 索引一致,改变背景颜色
if (mDownPosition == (i + 1)) {
mPaint!!.color = mDownBackGroundColor
} else {
mPaint!!.color = mRectBackGroundColor
}
6、wrap_content处理
在使用当前控件的时候,需要处理wrap_content的属性,否则效果就会和match_parent一样了,具体的处理如下,重写onMeasure方法,获取高度的模式后进行单独的设置。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
if (heightSpecMode == MeasureSpec.AT_MOST) {
//当高度为 wrap_content 时 设置一个合适的高度
setMeasuredDimension(widthSpecSize, (mHeight * 4 + mSpacing * 5 + 10).toInt())
}
}
四、源文件地址及总结
源文件地址:
源文件不是一个项目,是一个单纯的文件,大家直接复制到项目中使用即可,对于26个英文字母键盘绘制,基本上思路是一致的,大家可以在此基础上进行拓展,本文就先到这里吧,整体略有瑕疵,忘包含。
链接:https://juejin.cn/post/7166424996636524557
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。