欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Android自定义密码输入框的简单实现过程

程序员文章站 2022-06-18 13:18:42
目录一、实现效果及方案二、实现总结一、实现效果及方案预期效果图:如上图所示,要实现一个这种密码输入框的样式,原生并未提供类似的效果,所以需要自定义控件的方式实现。预期的基础效果:只接受数字;支持输入加...

一、实现效果及方案

预期效果图:

Android自定义密码输入框的简单实现过程

如上图所示,要实现一个这种密码输入框的样式,原生并未提供类似的效果,所以需要自定义控件的方式实现。

预期的基础效果:

只接受数字;

支持输入加密显示;

支持删除;

密码位数可配置;

文字大小、颜色、数字框背景可配置;

方案分析:

需要解决的问题:

配置性;

输入、删除如何实现?

整体ui如何实现?

1.对于输入删除可以通过setonkeylistener监听软件盘的事件。

2.可配置性数据可以通过自定义的属性文件配置;

3.对于ui效果:

a:可以基于原生控件做开发,每一个数字布局对应一个textview,选用数据结构对其管理,再选用一种容器布局,比如linearlayout进行添加。

b:通过自定义view的方式开发,需要自行绘制,绘制的内容包括背景、及密码内容、密码加密样式内容。

二、实现

这里选用方案b的方式进行实现,尽量使用较少的控件去实现,使用a的方案至少要用到5个原生控件的组合。

1.继承viewgrop还是view?

如果选用方案a的话其实算是继承了viewgrop,而内部的单个数字则作为一个独立的子控件,这样的话是可以继承viewgrop的
,但显然不需要这么麻烦(需要处理layout等),这个密码输入就是一个独立的控件不需要再加入子控件,所以直接继承view。

class passwordedittext @jvmoverloads constructor(
    context: context,
    attributeset: attributeset? = null,
) : view(context, attributeset, 0) {
    
}

2.继承view的话就要处理 wrap_content,所以要重写onmeasure,即在未设置具体的宽度时也要能够正常的显示测量。先定义一些宽高颜色的变量:

 //密码位数
    private var passwordlength = 4
    private var textcolor = 0

    //间隔  ->   dp2px
    private var itempadding = 5

    //单个数字包括背景宽度 dp2px
    private var itemwidth = 30
    private var bgitemcolor = 0
    private val mpaintbg = paint()
    //用于存储输入后的密码
    private val password = arrayofnulls<string>(passwordlength)

宽度的话相对来说还是很好计算的:

override fun onmeasure(widthmeasurespec: int, heightmeasurespec: int) {
        super.onmeasure(widthmeasurespec, heightmeasurespec)
        var width = 0
        when (measurespec.getmode(widthmeasurespec)) {
            measurespec.unspecified,measurespec.at_most ->{
                width = itemwidth * passwordlength + itempadding * (passwordlength-1)
            }

            measurespec.exactly ->{
                width = measurespec.getsize(widthmeasurespec)
                itemwidth = (width - itempadding *(passwordlength -1)) / passwordlength
            }
        }
        setmeasureddimension(width,itemwidth)
    }

看着ui图基本可以算出来了,不涉及太复杂的计算,这里并未对高度进行处理,理论上高度的值应该用指定的就好了;
需要做的测量基本就是这些了,下面开始绘制背景了:

也很简单根据 passwordlength 循环绘制圆角矩形就ok了,而参数的话也很好计算出来:

private fun drawbgitems(canvas: canvas) {
        for (i in password.indices) {
            未处理padding值 加上即可
            val rect = rectf(
                (i * itemwidth + (i) * itempadding).tofloat(),
                0f,
                ((i+1) * itemwidth + i * itempadding).tofloat(),
                itemwidth.tofloat()
            )
            canvas.drawroundrect(rect, 5f, 5f, mpaintbg)
        }
    }

为了让效果更明显,先画了一个颜色鲜艳的:

Android自定义密码输入框的简单实现过程

背景的话就绘制ok了,下面要做的就是监听事件再绘制密码内容了;

要实现一个onkeylistener

 //键盘监听
    private val keylistener = onkeylistener { v, keycode, event ->
        val action = event.action
        if (action == keyevent.action_down) {
            if (keycode == keyevent.keycode_del) {
                //删除
                return@onkeylistener true
            }
            if (keycode >= keyevent.keycode_0 && keycode <= keyevent.keycode_9) {
                 //数字键

            }
            if (keycode == keyevent.keycode_enter) {
                //确认键
                return@onkeylistener true
            }
        }

        return@onkeylistener false
    }

这里只是添加好了回调条件,还要做相应的处理。但键盘还没弹出,所以要先处理点击事件调用系统的方法主动弹出软键盘才行

 override fun ontouchevent(event: motionevent?): boolean {
        if (event!!.action == motionevent.action_down) {
            //获取焦点
            requestfocus()
            //getcontext().getsystemservice(context.input_method_service)
            inputmanager.showsoftinput(this, inputmethodmanager.show_forced)
            return true
        }
        return super.ontouchevent(event)
    }

重写ontouchevent之后就可以拦截点击事件弹出键盘拉。而view和软键盘的联系需要通过oncreateinputconnection 来实现,具体可以看下源码的介绍。

下面接着处理监听事件,首先是在接受到数字输入时的处理,要把输入的数字存储到容器并绘制出来

这里需要注意keycode 的值,看下源码并不是keycode_0 就是0了

Android自定义密码输入框的简单实现过程

再存储一下输入的数字:

 if (keycode >= keyevent.keycode_0 && keycode <= keyevent.keycode_9) {
                 //数字键
                password[currentinputposition] = (keycode - 7).tostring()
                currentinputposition++
                postinvalidate()
                return@onkeylistener true
            }

currentinputposition为了标记当前操作的位置,方便添加和删除,因为都是从两头开始的用这个值就可以了。

下面开始绘制文字了,如果加密的话只需要在每个背景的中心画一个小黑点就ok了,或者直接画一个数字,根据基线用drawtext画就好了,而y轴的基线很好确定就是高度的一半像素减去高度文字一半,通过设置textalign = paint.align.center即可实现横向上的居中(会根据x基线),x轴的基线则需要计算一下,比如第一个框的x基线则应该是,框宽的一半再减去绘制文字宽度的一半,这样才能在中间,第二个框内x的基线应该是:paddingleft+1框宽+1padding+框宽/2

所以绘制文字的代码就出来了:

 //绘制文字
    private fun drawpasswordnumber(canvas: canvas) {
        for (i in password.indices) {
            if (password[i] != null) {
                //没有开启明文显示,绘制密码密文
                val txt = if (iscipherenable) cipherstring else password[i]
                mpainttv.gettextbounds(txt, 0, txt!!.length, recttv)
                val offset = (recttv.top + recttv.bottom) / 2
                canvas.drawtext(
                    password[i]!!,
                    (paddingleft + itemwidth * i + itempadding * i + itemwidth / 2).tofloat(),
                    (paddingtop + itemwidth / 2).tofloat() - offset, mpainttv
                )
            }
        }
    }

这里加了是否开启密文显示的开关,最终运行的效果如下:

Android自定义密码输入框的简单实现过程
Android自定义密码输入框的简单实现过程

这个黑点也可以通过画圆的方式进行绘制,但无法通过字符串进行动态配置。

再输入完四位以后在点的话就会数组越界闪退了,所以在完成相应位数的添加后要禁止再绘制。

if (keycode >= keyevent.keycode_0 && keycode <= keyevent.keycode_9) {
                //加入判断 currentinputposition 
                if (currentinputposition == passwordlength) {
                    return@onkeylistener true
                }
                password[currentinputposition] = (keycode - 7).tostring()
                currentinputposition++
                postinvalidate()
                return@onkeylistener true
            }

下面要处理删除操作了,删除要做的就是去除数组中保存的已输入密码,更新操作标记位,再刷新绘制就ok了

if (keycode == keyevent.keycode_del) {
                //删除
                if(currentinputposition == 0){
                    return@onkeylistener true
                }
                password[currentinputposition-1] = null
                currentinputposition--
                postinvalidate()
                return@onkeylistener true
            }

看下最后的ui效果:

Android自定义密码输入框的简单实现过程

下面可以提供一些对外的方法、接口,比如获取内容,输入删除确认的回调监听。

//获取输入内容
    fun gettextcontent():string {
        val sb = stringbuilder()
        for (p in password) {
            p?.let {
                sb.append(p)
            }
        }
        return sb.tostring()
    }

    //操作回调 加些需要的参数
    interface operationlistener{
        fun inputoperationcallback()
        
        fun completeoperationcallback()
        
        fun deleteoperationcallback()
    }

这里还可以继续开发其他一些主流的样式,比如下划线、网格的样式,但绘制思路基本相同,还可以加上一个任务类执行绘制光标的操作。

总结

一个简单的自定义view demo,自定义view的难点在于参数的计算和很多api一不用就会忘记,但是继承viewgroup还是view,测量绘制布局的过程以及基本的绘制方法配置还是要清楚些,或者说能想起来,对于太复杂难以开发的控件感觉如果有现成的还是可以直接用的,毕竟没那么多时间去调试一些复杂的参数,如果能写出来也很牛皮吧。