kotlin实现的简单个人账户管理APP(三) 自定义View仿支付宝的密码输入框/密码相关逻辑
轉載請注明出處: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仿支付宝的密码输入框/密码相关逻辑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从零基础到web前端工程师(三)
- 下一篇: html中用form单选框右侧提示汗字,