用Canvas绘制一个数字键盘

1,592 阅读6分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

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无参隐藏字母
setBackGroundColorint类型的颜色值设置整体背景色
setRectBackGroundColorint类型的颜色值设置数字格子背景色
setTextColorint类型的颜色值设置文字颜色
setTextSizeFloat设置数字大小
setNumberSizeint类型设置按下的数字总长度
setRectHeightFloat设置数字键盘每格高度
setSpacingFloat设置数字键盘每格间隔距离

属性介绍

属性类型概述
background_colorcolor背景颜色
rect_background_colorcolor数字格子背景色
down_background_colorcolor手指按下的背景颜色
text_colorcolor文字颜色
text_sizedimension文字大小
letter_text_sizedimension字母的文字大小
rect_heightdimension数字格子高度
rect_spacingdimension格子间距
is_rect_letterboolean是否显示字母
number_sizeinteger按下的数字总长度字符

三、具体代码实现

代码实现上其实也没有什么难的,主要就是用到了自定义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())
        }
    }

四、源文件地址及总结

源文件地址:

github.com/AbnerMing88…

源文件不是一个项目,是一个单纯的文件,大家直接复制到项目中使用即可,对于26个英文字母键盘绘制,基本上思路是一致的,大家可以在此基础上进行拓展,本文就先到这里吧,整体略有瑕疵,忘包含。