需求
限制输入长度
输入字符下有下划线
有光标闪烁
触摸时自动弹出输入法
和editText一样有hint 提示语
输入完毕回调
因为需求要求不是很多,所以暂时实现的比较简单
实现语言
//首先创建一个class 继承View/** * 密码输入框 * 支持多位 */class InputKeyWordView : View, View.OnFocusChangeListener { interface OnKeyWordChangedListener { fun onkeyWordChange(keyWord: String, keyWordView: InputKeyWordView) } companion object { private val defaultTxtColr: Int = Color.parseColor("#1A1A1A") private val defaultHintColor: Int = Color.parseColor("#9B9B9B") private val defaultCursorColor: Int = Color.parseColor("#9B9B9B") } /** * 输入类型 */ interface InputType { companion object { val NUMBER: Int get() = 0x11 val NUMBER_CHAR: Int get() = 0x12 } } class InputMatch { companion object { private val number_reg = "\\d" private val numberChar_reg = "\\w" val patternNumber = Pattern.compile(number_reg) val patternCharNumber = Pattern.compile(numberChar_reg) } } //msgWhat private val alphaMsg = 0x111 val mHandler: Handler = @SuppressLint("HandlerLeak") object : Handler() { override fun handleMessage(msg: Message) { if (msg.what == alphaMsg) { onMessage() } } } private fun onMessage() { animEnableShow = !animEnableShow postInvalidate() mHandler.sendEmptyMessageDelayed(alphaMsg, ALPHA_ANIM_DELAY) } //闪动间隔时间 private val ALPHA_ANIM_DELAY: Long = 800 //光标 宽度 private val cursorWidth: Float //光标颜色 var cursorColor: Int set(value) { field = value cursorPaint.color = cursorColor invalidate() } // 是否 底部线 var showBottomLine: Boolean set(value) { field = value invalidate() } //底部线的颜色 var bottomLineColor: Int set(value) { field = value linePaint.color = bottomLineColor invalidate() } //底部线的高度 var bottomLineHeight: Float set(value) { field = value invalidate() } //提示字 var hint: String set(value) { field = value hintPaint.getTextBounds(hint, 0, hint.length, hintBounds) invalidate() } //hint颜色 var hintColor: Int set(value) { field = value hintPaint.color = hintColor invalidate() } //字的颜色 var txtColor: Int set(value) { field = value txtPaint.color = txtColor invalidate() } //字体大小 var txtSize: Float set(value) { field = value hintPaint.textSize = txtSize txtPaint.textSize = txtSize invalidate() } /** * 输入类型 * [InputKeyWordView.InputType] */ var inputType: Int set(value) { field = value invalidate() } //输入位数 var inputLength: Int set(value) { field = value invalidate() } // ================================= private val hintPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) private var txtPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) private var linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL } private var cursorPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL } private val lineInteval: Int /** * 闪动第几个 下划线 */// private var alphaLine = 0 //是否闪动 private var alphaEnable = false private var animEnableShow = true //文字大小 private val hintBounds = Rect() //输入的文字大小 private val inputBounds = Rect() //输入的字符 private val inputCharArray = StringBuilder() private val inputMethodManager: InputMethodManager // 输入完成监听 var onKeyWordChangedListener: OnKeyWordChangedListener? = null constructor(context: Context) : this(context, null) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { val typedArray = context.obtainStyledAttributes(attrs, R.styleable.InputKeyWordView) showBottomLine = typedArray.getBoolean(R.styleable.InputKeyWordView_ipv_show_bottom_line, true) bottomLineColor = typedArray.getColor(R.styleable.InputKeyWordView_ipv_bottom_line_color, defaultHintColor) bottomLineHeight = typedArray.getDimension(R.styleable.InputKeyWordView_ipv_bottom_line_height, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, context.resources.displayMetrics)) hintColor = typedArray.getColor(R.styleable.InputKeyWordView_ipv_hint_color, defaultHintColor) txtColor = typedArray.getColor(R.styleable.InputKeyWordView_ipv_show_txt_color, defaultTxtColr) txtSize = typedArray.getDimension(R.styleable.InputKeyWordView_ipv_txt_size, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14f, context.resources.displayMetrics)) hint = typedArray.getString(R.styleable.InputKeyWordView_ipv_hint_text) ?: String() cursorColor = typedArray.getColor(R.styleable.InputKeyWordView_ipv_cursor_color, defaultCursorColor) inputType = typedArray.getInt(R.styleable.InputKeyWordView_ipv_input_type, InputType.NUMBER) inputLength = typedArray.getInt(R.styleable.InputKeyWordView_ipv_input_length, 6) typedArray.recycle() isFocusable = true onFocusChangeListener = this inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager //下划线和文字间隔 lineInteval = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics).toInt() cursorWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, resources.displayMetrics) //=========================// hintPaint.color = hintColor// hintPaint.textSize = txtSize//// txtPaint.color = txtColor// txtPaint.textSize = txtSize//// linePaint.color = bottomLineColor// linePaint.style = Paint.Style.FILL// hintPaint.getTextBounds(hint, 0, hint.length, hintBounds) } /** * 清除已输入的字符 */ public fun clearInputKeyWord() { if (inputCharArray.isNotEmpty()) { inputCharArray.delete(0, inputCharArray.lastIndex) drawHint = true invalidate() } } //获取输入的 public fun getInputKeyWord(): String { return inputCharArray.toString() } override fun onCheckIsTextEditor(): Boolean { return true } override fun onFocusChange(v: View, hasFocus: Boolean) { alphaEnable = hasFocus if (hasFocus) { mHandler.sendEmptyMessageDelayed(alphaMsg, ALPHA_ANIM_DELAY) openKeyBroad() } else { mHandler.removeMessages(alphaMsg) closeKeyBroad() } println("onFocusChange:::::::$hasFocus") } private fun openKeyBroad() { inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) } private fun closeKeyBroad() { inputMethodManager.hideSoftInputFromInputMethod(windowToken, InputMethodManager.HIDE_IMPLICIT_ONLY) }//重写onCreateInputConnection 用来设置与键盘的链接 override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection { println("onCreateInputConnection") outAttrs.inputType = when (inputType) { InputType.NUMBER -> EditorInfo.TYPE_CLASS_NUMBER InputType.NUMBER_CHAR -> EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD else -> EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD }//BaseInputConnection 是View 内 用来处理 键盘输入的类 return object : BaseInputConnection(this, false) { override fun commitText(text: CharSequence, newCursorPosition: Int): Boolean {// println("commitText:::::::" + text) if (when (inputType) { InputType.NUMBER -> { InputMatch.patternNumber.matcher(text).matches() } InputType.NUMBER_CHAR -> { InputMatch.patternCharNumber.matcher(text).matches() } else -> false }) { addKey(text) } return true } override fun sendKeyEvent(event: KeyEvent): Boolean { if (event.action == KeyEvent.ACTION_UP) { when (event.keyCode) { KeyEvent.KEYCODE_ENTER -> { closeKeyBroad() } KeyEvent.KEYCODE_DEL -> { deleteKey() } } } return super.sendKeyEvent(event) } } }//模拟点击时 获取焦点 弹起键盘 override fun performClick(): Boolean { isFocusableInTouchMode = true requestFocus() openKeyBroad() return super.performClick() }//这里 监听手势,按下时开始处理 @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { if (event.action == MotionEvent.ACTION_DOWN) { isFocusableInTouchMode = true requestFocus() openKeyBroad() } return super.onTouchEvent(event) }//添加输入的字符 放入 字符数组内 private fun addKey(key: CharSequence) { if (inputCharArray.length < inputLength) { drawHint = false inputCharArray.append(key)// alphaLine = inputCharArray.length invalidate() if (null != onKeyWordChangedListener) { onKeyWordChangedListener?.onkeyWordChange(inputCharArray.toString(), this) } } }// 每次点击删除键 删除输入的字符 private fun deleteKey() { if (inputCharArray.isNotEmpty()) { drawHint = false inputCharArray.deleteCharAt(inputCharArray.lastIndex)// alphaLine = inputCharArray.length invalidate() } if (inputCharArray.isEmpty()) {// alphaLine = 0 drawHint = true invalidate() } if (null != onKeyWordChangedListener) { onKeyWordChangedListener?.onkeyWordChange(inputCharArray.toString(), this) } } @SuppressLint("SwitchIntDef") override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)) } //测量宽度 private fun measureWidth(widthMeasureSpec: Int): Int { val widthMode = MeasureSpec.getMode(widthMeasureSpec) var width = suggestedMinimumWidth val size = MeasureSpec.getSize(widthMeasureSpec) when (widthMode) { //View想要到多少就到多少,没有限制 MeasureSpec.AT_MOST //未指定大小,任意// , MeasureSpec.UNSPECIFIED -> { width = MeasureSpec.getSize(MeasureSpec.AT_MOST) } //View指定大小 MeasureSpec.EXACTLY -> { width = size } // //任意大小 MeasureSpec.UNSPECIFIED -> { width = Math.max(width, size) } } return width } private fun measureHeight(widthMeasureSpec: Int): Int { val heightMode = MeasureSpec.getMode(widthMeasureSpec) var height = suggestedMinimumHeight val size = MeasureSpec.getSize(widthMeasureSpec) when (heightMode) { //根据自己的视图决定,想到多少就到多少 MeasureSpec.AT_MOST //未指定大小// , MeasureSpec.UNSPECIFIED -> { height = hintBounds.height() + paddingTop + paddingBottom + lineInteval } //指定高度 MeasureSpec.EXACTLY -> { height = size } //未指定,任意大小 MeasureSpec.UNSPECIFIED -> { //取最大值 height = Math.max(height, size) } } return height } private var drawHint: Boolean = true override fun onDraw(canvas: Canvas) { if (drawHint) { drawHintTxt(canvas) } drawInputChar(canvas) drawBottomLine(canvas) } //输入的字符 private fun drawInputChar(canvas: Canvas) { val eachWidth = (measuredWidth - lineInteval * (inputLength - 1)) / inputLength for (index in 0..inputLength) { if (inputCharArray.length > index) { val char = inputCharArray[index].toString() txtPaint.getTextBounds(char, 0, char.length, inputBounds) val y: Float = ((measuredHeight + inputBounds.height()) / 2).toFloat() - txtPaint.fontMetrics.bottom / 2 //中间点x val startX: Float = (eachWidth / 2 + index * (lineInteval + eachWidth) - inputBounds.width() / 2).toFloat() canvas.drawText(char , 0 , char.length , startX , y , txtPaint) } else break } } //底部下划线 和光标 private fun drawBottomLine(canvas: Canvas) { val eachWidth = (measuredWidth - lineInteval * (inputLength - 1)) / inputLength val y = (measuredHeight - paddingBottom - bottomLineHeight) val bottom = y + bottomLineHeight for (index in 0..(inputLength - 1)) { val startX = index * (eachWidth + lineInteval) val stopX = startX + eachWidth if (showBottomLine) { //底部下划线 canvas.drawRect(startX.toFloat(), y, stopX.toFloat(), bottom, linePaint) } //获取焦点光标闪动 if (alphaEnable) { // 当前位置 if (inputCharArray.length == index) { if (animEnableShow) {//光标显示 val cursorX = startX + eachWidth / 2f canvas.drawRect(cursorX , (measuredHeight - hintBounds.height()) / 2f , cursorX + cursorWidth , (measuredHeight + hintBounds.height()) / 2f , cursorPaint) } } } } } //画提示字 private fun drawHintTxt(canvas: Canvas) { if (inputCharArray.isEmpty() && !alphaEnable) { val y: Float = ((measuredHeight + hintBounds.height()) / 2).toFloat() - hintPaint.fontMetrics.bottom / 2 canvas.drawText(hint, 0, hint.length, 0f, y, hintPaint) } } }
在attr.xml中配置 自定义属性
需要的可以增加 属性
<!--密码输入框--> <declare-styleable name="InputKeyWordView"> <!--输入位数 默认4位--> <attr name="ipv_input_length" format="integer" /> <!--底部线--> <attr name="ipv_show_bottom_line" format="boolean" /> <!--底部线高度--> <attr name="ipv_bottom_line_height" format="dimension" /> <!--底部线的颜色--> <attr name="ipv_bottom_line_color" format="color" /> <!--显示字的颜色--> <attr name="ipv_show_txt_color" format="color" /> <!--显示字的大小--> <attr name="ipv_txt_size" format="dimension" /> <!--提示字--> <attr name="ipv_hint_text" format="string" /> <!--提示颜色--> <attr name="ipv_hint_color" format="color" /> <!--光标颜色--> <attr name="ipv_cursor_color" format="color" /> <!--输入类型--> <attr name="ipv_input_type" format="enum"> <!--数字--> <enum name="number" value="0x11" /> <!--数字和字母--> <enum name="numChar" value="0x12" /> </attr> </declare-styleable>
使用方式
<com.zfzx.investor.custom.weight.InputKeyWordView android:id="@+id/ipv_sms_code" android:layout_width="0dp" android:layout_height="50dp" android:layout_weight="1" android:paddingBottom="2dp" app:ipv_bottom_line_color="?attr/gray_1" app:ipv_bottom_line_height="1dp" app:ipv_hint_color="?attr/black_gray_1" app:ipv_hint_text="@string/sms_verify_code" app:ipv_input_length="6" app:ipv_input_type="numChar" app:ipv_show_bottom_line="true" app:ipv_show_txt_color="?attr/black_1" app:ipv_txt_size="14sp" />
作者:Mocaris
链接:https://www.jianshu.com/p/b794750c05a1