经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
Android自定义密码输入框的简单实现过程
来源:jb51  时间:2021/11/9 15:50:50  对本文有异议

一、实现效果及方案

预期效果图:


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

预期的基础效果:

只接受数字;

支持输入加密显示;

支持删除;

密码位数可配置;

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

方案分析:

需要解决的问题:

配置性;

输入、删除如何实现?

整体UI如何实现?

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

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

3.对于UI效果:

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

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

二、实现

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

1.继承ViewGrop还是View?

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

  1. class PasswordEditText @JvmOverloads constructor(
  2. context: Context,
  3. attributeSet: AttributeSet? = null,
  4. ) : View(context, attributeSet, 0) {
  5. }

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

  1. //密码位数
  2. private var passwordLength = 4
  3. private var textColor = 0
  4.  
  5. //间隔 -> dp2px
  6. private var itemPadding = 5
  7.  
  8. //单个数字包括背景宽度 dp2px
  9. private var itemWidth = 30
  10. private var bgItemColor = 0
  11. private val mPaintBg = Paint()
  12. //用于存储输入后的密码
  13. private val password = arrayOfNulls<String>(passwordLength)

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

  1. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  2. super.onMeasure(widthMeasureSpec, heightMeasureSpec)
  3. var width = 0
  4. when (MeasureSpec.getMode(widthMeasureSpec)) {
  5. MeasureSpec.UNSPECIFIED,MeasureSpec.AT_MOST ->{
  6. width = itemWidth * passwordLength + itemPadding * (passwordLength-1)
  7. }
  8.  
  9. MeasureSpec.EXACTLY ->{
  10. width = MeasureSpec.getSize(widthMeasureSpec)
  11. itemWidth = (width - itemPadding *(passwordLength -1)) / passwordLength
  12. }
  13. }
  14. setMeasuredDimension(width,itemWidth)
  15. }

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

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

  1. private fun drawBgItems(canvas: Canvas) {
  2. for (i in password.indices) {
  3. 未处理padding 加上即可
  4. val rect = RectF(
  5. (i * itemWidth + (i) * itemPadding).toFloat(),
  6. 0f,
  7. ((i+1) * itemWidth + i * itemPadding).toFloat(),
  8. itemWidth.toFloat()
  9. )
  10. canvas.drawRoundRect(rect, 5f, 5f, mPaintBg)
  11. }
  12. }

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


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

要实现一个OnKeyListener

  1. //键盘监听
  2. private val keyListener = OnKeyListener { v, keyCode, event ->
  3. val action = event.action
  4. if (action == KeyEvent.ACTION_DOWN) {
  5. if (keyCode == KeyEvent.KEYCODE_DEL) {
  6. //删除
  7. return@OnKeyListener true
  8. }
  9. if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
  10. //数字键
  11.  
  12. }
  13. if (keyCode == KeyEvent.KEYCODE_ENTER) {
  14. //确认键
  15. return@OnKeyListener true
  16. }
  17. }
  18.  
  19. return@OnKeyListener false
  20. }

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

  1. override fun onTouchEvent(event: MotionEvent?): Boolean {
  2. if (event!!.action == MotionEvent.ACTION_DOWN) {
  3. //获取焦点
  4. requestFocus()
  5. //getContext().getSystemService(Context.INPUT_METHOD_SERVICE)
  6. inputManager.showSoftInput(this, InputMethodManager.SHOW_FORCED)
  7. return true
  8. }
  9. return super.onTouchEvent(event)
  10. }

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

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

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


再存储一下输入的数字:

  1. if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
  2. //数字键
  3. password[currentInputPosition] = (keyCode - 7).toString()
  4. currentInputPosition++
  5. postInvalidate()
  6. return@OnKeyListener true
  7. }

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

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

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

  1. //绘制文字
  2. private fun drawPasswordNumber(canvas: Canvas) {
  3. for (i in password.indices) {
  4. if (password[i] != null) {
  5. //没有开启明文显示,绘制密码密文
  6. val txt = if (isCipherEnable) cipherString else password[i]
  7. mPaintTv.getTextBounds(txt, 0, txt!!.length, rectTv)
  8. val offset = (rectTv.top + rectTv.bottom) / 2
  9. canvas.drawText(
  10. password[i]!!,
  11. (paddingLeft + itemWidth * i + itemPadding * i + itemWidth / 2).toFloat(),
  12. (paddingTop + itemWidth / 2).toFloat() - offset, mPaintTv
  13. )
  14. }
  15. }
  16. }

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



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

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

  1. if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
  2. //加入判断 currentInputPosition
  3. if (currentInputPosition == passwordLength) {
  4. return@OnKeyListener true
  5. }
  6. password[currentInputPosition] = (keyCode - 7).toString()
  7. currentInputPosition++
  8. postInvalidate()
  9. return@OnKeyListener true
  10. }

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

  1. if (keyCode == KeyEvent.KEYCODE_DEL) {
  2. //删除
  3. if(currentInputPosition == 0){
  4. return@OnKeyListener true
  5. }
  6. password[currentInputPosition-1] = null
  7. currentInputPosition--
  8. postInvalidate()
  9. return@OnKeyListener true
  10. }

看下最后的UI效果:


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

  1. //获取输入内容
  2. fun getTextContent():String {
  3. val sb = StringBuilder()
  4. for (p in password) {
  5. p?.let {
  6. sb.append(p)
  7. }
  8. }
  9. return sb.toString()
  10. }
  11.  
  12. //操作回调 加些需要的参数
  13. interface OperationListener{
  14. fun inputOperationCallBack()
  15. fun completeOperationCallBack()
  16. fun deleteOperationCallBack()
  17. }

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

总结

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号