需求限制输入长度
输入字符下有下划线
有光标闪烁
触摸时自动弹出输入法
和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
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中配置 自定义属性
需要的可以增加 属性
使用方式
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