转载请注明出处:http://blog.csdn.net/a512337862/article/details/78874322

前言

1.本篇博客相关的项目介绍请参考基于kotlin实现的简单个人账户管理APP
2.本篇博客是介绍利用kotlin 自定义View实现仿支付宝密码输入框以及密码输入相关逻辑。
3.因为本人是kotlin初学者,博客如果有任何问题请指出。

截图效果

代码分析

仿支付宝密码输入框

InputPswView

代码如下:

/*** Author : BlackHao* Time : 2017/12/7 14:36* Description : 自定义密码输入框*/class InputPswView(context: Context?, attrs: AttributeSet?) : EditText(context, attrs), TextWatcher {//边框颜色private var strokeColor: Int = Color.GRAY//圆角private var roundRadius: Int = 10//边框宽度private var strokeWidth = 4//画笔private var paint: Paint//密码字符数private var pswCount = 6//边框rectprivate var rectF: RectFprivate var roundRectF: RectF//Path用于圆角绘制边框private var roundPath: Path//输入完成监听lateinit var listener: InputListenerinit {val a = context?.obtainStyledAttributes(attrs, R.styleable.InputPswView, 0, 0)val n = a?.indexCount(0 until n!!).map { a.getIndex(it) }.forEach {when (it) {R.styleable.InputPswView_strokeColor -> strokeColor = a.getColor(it, Color.GRAY)R.styleable.InputPswView_roundRadius -> roundRadius = a.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 5f, resources.displayMetrics)).toInt()R.styleable.InputPswView_pswCount -> pswCount = a.getInt(it, 6)R.styleable.InputPswView_strokeWidth -> strokeWidth = a.getDimension(it, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, resources.displayMetrics)).toInt()}}//recyclea.recycle()//初始化画笔paint = Paint()paint.isAntiAlias = truepaint.strokeWidth = strokeWidth.toFloat()paint.style = Paint.Style.STROKErectF = RectF()roundRectF = RectF()roundPath = Path()//隐藏光标isCursorVisible = false//取消下划线background = ColorDrawable(Color.TRANSPARENT)//设置输入监听addTextChangedListener(this)//设置最大长度filters = arrayOf(InputFilter.LengthFilter(pswCount))}override fun onDraw(canvas: Canvas?) {
//        super.onDraw(canvas)//通过密码数获取输入框宽度val boxWidth = (width / pswCount).toFloat()//设置画笔为strokepaint.style = Paint.Style.STROKE//设置颜色为strokeColorpaint.color = strokeColor//判断roundRadius,必须小于width / 2 以及 height / 2if (roundRadius > width / 2 || roundRadius > height / 2) {roundRadius = if (width > height) height / 2 else width / 2}//这里stroke是为了让边框能完全显示,如果rectF.set(0,0,width,height),那么边框会有一半因为超出画布范围而无法显示val stroke = strokeWidth / 2frectF.set(stroke, stroke, width - stroke, height - stroke)//利用Path绘制最外层边框roundPath.reset()roundPath.moveTo(rectF.left, rectF.top + roundRadius)roundRectF.set(rectF.left, rectF.top,rectF.left + roundRadius * 2, rectF.top + roundRadius * 2)roundPath.arcTo(roundRectF, 180F, 90F)roundPath.lineTo(rectF.right - roundRadius, rectF.top)roundRectF.set(rectF.right - roundRadius * 2, rectF.top,rectF.right, rectF.top + roundRadius * 2)roundPath.arcTo(roundRectF, 270F, 90F)roundPath.lineTo(rectF.right, rectF.bottom - roundRadius)roundRectF.set(rectF.right - roundRadius * 2, rectF.bottom - roundRadius * 2,rectF.right, rectF.bottom)roundPath.arcTo(roundRectF, 0F, 90F)roundPath.lineTo(rectF.left + roundRadius, rectF.bottom)roundRectF.set(rectF.left, rectF.bottom - roundRadius * 2,rectF.left + roundRadius * 2, rectF.bottom)roundPath.arcTo(roundRectF, 90F, 90F)roundPath.lineTo(rectF.left, rectF.top + roundRadius)roundPath.close()canvas?.drawPath(roundPath, paint)//绘制内部边框for (i in 1 until pswCount) {canvas?.drawLine(boxWidth * i, 0f, boxWidth * i, height.toFloat(), paint)}//获取输入的字符val inputLen = text.length//设置画笔为FILLpaint.style = Paint.Style.FILL//设置颜色为Blackpaint.color = Color.BLACK//绘制黑点if (inputLen >= 1) {for (i in 1..inputLen) {canvas?.drawCircle(boxWidth * i - boxWidth / 2, (height / 2).toFloat(), boxWidth / 4, paint)}}}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)val heightSize = View.MeasureSpec.getSize(heightMeasureSpec)//计算View的宽高var finalWidth = 0var finalHeight = 0if (widthMode == View.MeasureSpec.EXACTLY) {//指定大小或者match_parentfinalWidth = widthSize} else if (widthMode == View.MeasureSpec.AT_MOST) {//wrap_contentfinalWidth = pswCount * 100}if (heightMode == View.MeasureSpec.EXACTLY) {//指定大小或者match_parentfinalHeight = heightSize} else if (heightMode == View.MeasureSpec.AT_MOST) {//wrap_contentfinalHeight = 100}setMeasuredDimension(finalWidth, finalHeight)}override fun afterTextChanged(s: Editable?) {val textInput = s.toString()if (textInput.length == pswCount) {listener.inputFinish(this, textInput)}}override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}override fun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int) {super.onTextChanged(text, start, lengthBefore, lengthAfter)}//输入结束回调interface InputListener {fun inputFinish(view: View, text: String)}}

这里简单解释一下部分代码:
1.在init{}中进行初始化,获取相关的自定义属性,添加TextChangedListener。
2.onMeasure()中没有太多可说的,当宽高为wrap_content,即widthMode == View.MeasureSpec.AT_MOST时,将宽设置为密码长度*100,heightMode == View.MeasureSpec.AT_MOST时,将高设置为100。
3.ondraw()中super.onDraw(canvas)必须注释掉,不然EditText的文本仍然会显示出来。最外层圆角边框是利用path来实现的,只需要设置一下roundRadius即可,另外,在绘制最外层边框时,还是需要注意的一点就是 : 绘制有边框的线条时,线条实际的位置是在边框的中间,所有如果直接以canvas的边缘画边框的话,会有一半因为超出canvas的范围无法显示!具体的可以参考:http://blog.csdn.net/a512337862/article/details/74161988,代码截图如下:

4.afterTextChanged()中判断密码长度是否已经达到设置的最大长度,到达则将密码回调。

attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><!--分屏按钮--><declare-styleable name="InputPswView"><attr name="strokeColor" format="color" /><attr name="pswCount" format="integer" /><attr name="roundRadius" format="dimension" /><attr name="strokeWidth" format="dimension" /></declare-styleable>
</resources>

简单介绍一下各个自定义属性的含义:
1.strokeColor :边框颜色
2.pswCount :密码数量
3.roundRadius:圆角弧度,最大值不能超过view 宽高 的一半
4.strokeWidth: 边框宽度

密码输入相关逻辑

PswInputFragment

PswInputFragment继承于DialogFragment,用于以Dialog的形式弹出密码输入框,代码如下:

/*** Author : BlackHao* Time : 2017/8/31 15:27* Description : 删除 account Dialog*/class PswInputFragment : DialogFragment(), InputPswView.InputListener {//当前操作信息private lateinit var infoTv: TextView//输入信息TextViewprivate lateinit var typeTv: TextView//密码输入InputPswViewprivate lateinit var inputPswView: InputPswView//当前输入类型private lateinit var currentType: String//新密码private lateinit var newPsw: String//结果监听lateinit var listener: CheckPswListener//Applicationprivate lateinit var app: AccountAppoverride fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {val view = inflater!!.inflate(R.layout.dialog_psw_input, container, false)//初始化控件typeTv = view.findViewById(R.id.type_tv) as TextViewinfoTv = view.findViewById(R.id.info_tv) as TextViewinputPswView = view.findViewById(R.id.input_psw_view) as InputPswView//添加监听inputPswView.listener = this//初始化密码相关app = activity.application as AccountAppreturn view}override fun onResume() {super.onResume()when (tag) {//修改密码MODIFY_PSW -> {infoTv.text = getString(R.string.modify_psw)currentType = if (app.psw.isEmpty()) {//当前不存在密码时typeTv.setText(R.string.input_new_psw)ADD_NEW_PSW} else {//直接输入密码typeTv.setText(R.string.input_old_psw)INPUT_OLD_PSW}}//输入密码CHECK_PSW -> {infoTv.text = getString(R.string.login)typeTv.setText(R.string.input_psw)currentType = CHECK_PSW}//登录密码开关SWITCH_PSW -> {if (app.isPswLogin) {infoTv.text = getString(R.string.close_login)} else {infoTv.text = getString(R.string.open_login)}currentType = if (app.psw.isEmpty()) {//当前不存在密码时typeTv.setText(R.string.input_new_psw)ADD_NEW_PSW} else {//直接输入密码typeTv.setText(R.string.input_psw)CHECK_PSW}}}}override fun inputFinish(view: View, text: String) {inputPswView.setText("")when (currentType) {//密码检查CHECK_PSW -> {listener.checkResult(text == app.psw, tag)}//输入原密码INPUT_OLD_PSW -> {if (app.psw == text) {currentType = ADD_NEW_PSWtypeTv.setText(R.string.input_new_psw)} else {Toast.makeText(activity, R.string.error_psw, Toast.LENGTH_SHORT).show()dismiss()}}//输入新密码ADD_NEW_PSW -> {newPsw = texttypeTv.setText(R.string.ensure_new_psw)currentType = ENSURE_PSW}//确认新密码ENSURE_PSW -> {if (newPsw == text) {listener.checkResult(true, tag)//保存密码app.savePsw(newPsw)dismiss()} else {dismiss()Toast.makeText(activity, R.string.different_psw, Toast.LENGTH_SHORT).show()}}}}companion object {//新增密码val ADD_NEW_PSW = "addNew"//确认密码val ENSURE_PSW = "ensurePsw"//输入原密码val INPUT_OLD_PSW = "oldPsw"//修改密码val MODIFY_PSW = "modifyPsw"//输入密码val CHECK_PSW = "checkPsw"//开启关闭密码登录val SWITCH_PSW = "switchPsw"}}

这里简单解释一下部分代码:
1.静态常量用于表示当前调用PswInputFragment的情况(新增密码/确认密码等),然后根据不同的情况需要进行不同的逻辑处理。

2.因为这里涉及到密码输入/密码修改/开启(关闭)密码输入三种情况,以及同时修改标题栏文字,这里以DialogFragment.show(FragmentManager manager, String tag)中的tag来判断。主要在DialogFragment的onResume中来进行判断,具体内容请参考代码:

3.与2类似,密码输入完成后,同样需要判断密码检查/输入原密码/输入新密码/确认新密码等多种情况,需要做不同的处理,具体内容请参考代码::

结语

1.因为文字功底有限,所以介绍性的文字不多,但是基本上每句代码都加了注释,理解起来应该不难,如果有任何问题,可以留言。
2.这里只附上了我认为比较关键的代码,布局文件等都未涉及,需要的可以去下载源码。
3.项目源码下载地址:http://download.csdn.net/download/a512337862/10151418

kotlin实现的简单个人账户管理APP(三) 自定义View仿支付宝的密码输入框/密码相关逻辑相关推荐

  1. 基于kotlin实现的简单个人账户管理APP

    转载请注明出处:http://blog.csdn.net/a512337862/article/details/78753608 前言 因为本人的账户越来越多,有点记不过来,一直准备写一个简单的个人账 ...

  2. Android自定义控件开发系列(三)——仿支付宝六位支付密码输入页面

    在移动互联领域,有那么几家龙头一直是我等学习和追求的目标,比如支付宝.微信.饿了么.酷狗音乐等等,大神举不胜举,他们设计的界面.交互方式已经培养了中国(有可能会是世界)民众的操作习惯:举个小例子,对话 ...

  3. Kotlin实现一个简单的安卓app答题系统(含web服务端)

    目录 环境 成果 数据库设计 安卓端设计 演示结果 注意事项 压缩包 (注:本次是学校小学期课程的实验作业,本代码总共由三个人合作完成,也是基于学习其他博客的方式,同时揉和了我们组自己的想法,我在此仅 ...

  4. 零门槛!ZBLibrary仿微信朋友圈自定义View,就是这么简单!

    2019独角兽企业重金招聘Python工程师标准>>> 传统方法是继承现有View再重写方法,这种方式缺点很多: 1.往往不能在xml编辑器中预览效果: 2.比较难实现预期效果,比如 ...

  5. 初学Kotlin——在自定义View里的应用

    什么是Kotlin Kotlin,它是JetBrains开发的基于JVM的面向对象的语言.2017年的时候被Google推荐Android的官方语言,同时Android studio 3.0正式支持这 ...

  6. android 自定义view: 蛛网/雷达图(三)

    本系列自定义View全部采用kt 系统mac android studio: 4.1.3 kotlin version1.5.0 gradle: gradle-6.5-bin.zip 本篇效果: 蛛网 ...

  7. Springboot电子病历管理APP毕业设计源码010350

    摘 要 本文针对电子病历管理等问题,对其进行研究分析,然后开发设计出电子病历管理APP以解决问题.电子病历管理APP主要功能模块包括:病历管理.在线医生列表.病人挂号.医药处方费管理等,采取本系统此次 ...

  8. (附源码)springboot电子病历管理APP 毕业设计 010350

    Springboot电子病历管理APP 摘 要 本文针对电子病历管理等问题,对其进行研究分析,然后开发设计出电子病历管理APP以解决问题.电子病历管理APP主要功能模块包括:病历管理.在线医生列表.病 ...

  9. Flutter For App——一个简单的豆瓣APP

    一个简单的豆瓣APP 效果视频 功能简述 功能 第三方库 接口简述 底部导航栏 效果图 实现 初始化BottomNavigationBarItem bottomNavigationBar 切换页面导致 ...

最新文章

  1. 单片机音频节奏灯_用C51写的单片机音乐彩灯程序
  2. 首发 | 驭势科技推出“东风网络”:如何找到速度-精度的最优解?| 技术头条...
  3. 硅谷产品实战-总结:19、增长黑客的核心公式
  4. python多个分隔符分割字符串_Python中带有多个分隔符的拆分字符串
  5. Tomcat原理详解和各种集群的实现(转自:http://harisxiong.blog.51cto.com/7513022/1304746)
  6. POJ2112 Optimal Milking
  7. resin指定java版本_resin的几个常用配置
  8. nginx 超时时间_Linux从入门到放弃 Nginx
  9. TensorFlow框架案例实战
  10. 浅谈软件项目验收(转)
  11. HTTP协议 (三) 代理
  12. 隐藏文件去掉隐藏属性
  13. Scacanner类
  14. ubuntu 刷新频率 如何查看_Ubuntu分辨率和刷新频率设置
  15. [4G5G专题-112]:部署 - LTE邻区规划、配置、自动邻区关系ANR
  16. 图论 —— 图的遍历 —— 哈密顿问题
  17. Ubuntu 16.04 一系列软件安装命令,包括QQ、搜狗、Chrome、vlc、网易云音乐安装方法(转载)...
  18. anaconda心得(虚拟环境)
  19. C#利用QQ游戏破解QQ密码
  20. 【目录】从苏宁电器到卡巴斯基(后传)

热门文章

  1. java 判断对象中所有属性都为空
  2. AHRS 原理算法+代码实现(好文记录)
  3. 相忘于江湖——记另一位朋友
  4. 猴子分桃子,递归算法简练通俗易懂
  5. opencv无法打开摄像头
  6. java gef_【插件开发】—— 12 GEF入门
  7. 计算机桌面怎么添加便签,如何在电脑桌面上添加便签 这些知识你不一定知道...
  8. java锁 -- 自旋锁
  9. OpenFaceswap 入门教程(1):软件安装篇
  10. pic16f1829 c语言,PIC16F1829 TIMER2初始化程序及应用