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)
        }
    }

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

背景的话就绘制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了

再存储一下输入的数字:

 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
                )
            }
        }
    }

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


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

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

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效果:

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

//获取输入内容
    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,测量绘制布局的过程以及基本的绘制方法配置还是要清楚些,或者说能想起来,对于太复杂难以开发的控件感觉如果有现成的还是可以直接用的,毕竟没那么多时间去调试一些复杂的参数,如果能写出来也很牛皮吧。

(0)

相关推荐

  • Android仿支付宝自定义密码输入框及安全键盘(密码键盘)

     0.前言 之前做过的项目里有运用到一个支付场景:用户办理业务时需要输入交易密码,并且可根据平台下发的支付方式进行选择.这与支付宝的密码输入方式十分相似,如果使用Android系统或者第三方软件的键盘,会有密码泄露的风险.因此,大多数的应用软件使用的是自定义的密码输入框及安全键盘. 由于密码输入方式需要实现一个从底部弹出的效果,因此总体上决定采用BottomSheetDialog来进行封装,同时为了提高安全性,还应该随机生成键盘上的数字,界面如下图所示:   首先新建一个PasswordInpu

  • Android自定义View实现微信支付密码输入框

    本文实例为大家分享了Android实现微信支付密码输入框的具体代码,供大家参考,具体内容如下 效果图 项目中使用到了支付密码功能,其实这类界面是比较常用的,涉及支付密码的输入的一般都会用到对的,所以单独地把这部分抽取出来,有需要的朋友可以拿去用哈! 效果就是支付,弹出密码框,输入密码,这个过程密码不可见,并且提供一个输入完毕的监听! 这个弹出层呢,其实就是一个DialogFragment,逻辑封装在其内部 一.弹出层进出动画 (anim文件) push_bottom_in.xml <?xml v

  • Android 自定义密码输入框实现代码

    效果 自定义密码输入框,项目的一个界面需求,我把这个自定义的输入框提取出来作为这次内容的题目. 输入前: 输入后: 输入1个字符就红一个圈圈,很简单的效果. 思路 1.自定义EditText. 2.背景为一个外圆环加内实心圆. 3.edittext的长度变化时候重新绘制背景或者红色环位置. 关键代码 代码其实也很简单,顺手拿资源的请到文末. 1.画背景 /** * 绘制背景外圆 */ private void drawOutRing(Canvas canvas) { mPaint.setColo

  • Android自定义密码输入框和数字键盘

    实现了一个自定义的密码输入框和自定义数字键盘,用作用户支付密码设置界面.先上效果图如下,方格样式,以及点击空白处隐藏软键盘. 控件实现清单: 1)集成于EditText的输入框控件:PasswordInputView.java 2)数字键盘工具类:NumKeyboardUtil.java 3)xml文件:number.xml 4)attrs样式 5)layout文件 具体内容: PasswordInputView.java public class PasswordInputView exten

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

    目录 一.实现效果及方案 二.实现 总结 一.实现效果及方案 预期效果图: 如上图所示,要实现一个这种密码输入框的样式,原生并未提供类似的效果,所以需要自定义控件的方式实现. 预期的基础效果: 只接受数字: 支持输入加密显示: 支持删除: 密码位数可配置: 文字大小.颜色.数字框背景可配置: 方案分析: 需要解决的问题: 配置性: 输入.删除如何实现? 整体UI如何实现? 1.对于输入删除可以通过setOnKeyListener监听软件盘的事件. 2.可配置性数据可以通过自定义的属性文件配置:

  • Android自定义时间轴的实现过程

    本文讲述Android自定义时间轴的实现过程,供大家参考,具体内容如下 相关视频链接: Android自定义控件系列 http://edu.csdn.net/course/detail/3719/65396 Android视频全系列 http://edu.csdn.net/course/detail/2741/43163 时间轴效果,实际上非常简单,就是listView中一个又一个的条目而已-.大家可以只关注一个条目. 首先展示一个条目的布局效果 <?xml version="1.0&qu

  • Android自定义View实现简单的圆形Progress效果

    先给大家展示下效果图,如果感觉不错,请参考实现思路: 我们要实现一个自定义的再一个圆形中绘制一个弧形的自定义View,思路是这样的: 先要创建一个类ProgressView,继承自View类,然后重写其中的两个构造方法,一个是一个参数的,一个是两个参数的,因为我们要在xml文件中使用该自定义控件,所以必须要定义这个两个参数的构造函数.创建完了这个类后,我们先不去管它,先考虑我们实现的这个自定义View,我们想让它的哪些部分可以由使用者自己指定,比如说这个Demo中我们让他的外面圆的外边框颜色和宽

  • Android自定义Gradle插件的详细过程

    一.Gradle 我们知道在我们现在使用Android Stduio开发Android项目的时候,Android Studio是基于Gradle来帮助我们构建,管理项目的. Gradle:Gradle是一个项目构建工具,用来帮助我们管理项目的依赖.打包.发布.部署等工作. Gradle是通过如build.gradle这种gradle脚本来进行项目构建的,所以我们对项目的构建配置都是可以写在gradle构建脚本中. gradle构建脚本使用的是Groovy语言,Groovy语言也是一种jvm语言,

  • Android自定义对话框的简单实现

    本文实例为大家分享了Android自定义对话框的具体实现代码,供大家参考,具体内容如下 1.定义对话框的布局 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent&quo

  • Android自定义View实现简单水波纹效果

    本文实例为大家分享了Android自定义View实现水波纹效果的具体代码,供大家参考,具体内容如下 效果如下: 原理 控制代码 //这里用的kotlin //主线程刷新控件  val mHandler = object : Handler() {         override fun handleMessage(msg: Message?) {             waterRippleView.refreshView()         }      //开启动画,开线程,延时刷新pe

  • Android自定义View实现简单炫酷的球体进度球实例代码

    前言 最近一直在研究自定义view,正好项目中有一个根据下载进度来实现球体进度的需求,所以自己写了个进度球,代码非常简单.先看下效果: 效果还是非常不错的. 准备知识 要实现上面的效果我们只要掌握两个知识点就好了,一个是Handler机制,用于发消息刷新我们的进度球,一个是clipDrawable.网上关于Handler的教程很多,这里重点介绍一下clipDrawable,进度球的实现全靠clipDrawable. clipDrawable 如下图所示:ClipDrawable和InsertDr

  • android自定义View实现简单五子棋游戏

    做一个五子棋练练手,没什么特别的,再复习一下自定义View的知识,onMeasure,MeasureSpec , onDraw以及OnTouchEvent方法等. 效果图 代码如下: package com.fivechess; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas;

随机推荐