自定义数字解锁功能,如下所示:
#### 自定义View; 在验证错误的时候,会有错误提示,以及抖动动画出现
transformationMethod 设置应用于此TextView显示的文本的转换(转换成星号):(这里的代码在自定义view里都有,我只是单独贴出来)
input.transformationMethod = object : TransformationMethod {
override fun onFocusChanged(
view: View?,
sourceText: CharSequence?,
focused: Boolean,
direction: Int,
previouslyFocusedRect: Rect?
) {
}
override fun getTransformation(source: CharSequence, view: View?): CharSequence {
return AsteriskPassword(source)
}
}
/**
* password to * display
*/
class AsteriskPassword(private val source: CharSequence) : CharSequence {
override val length: Int
get() = source.length
override fun get(index: Int): Char {
return '*'
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return source.subSequence(startIndex, endIndex)
}
}
1.CustomView >>> LockKeypad:(代码实现)
class LockKeypad @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : ConstraintLayout(context, attrs, defStyleAttr) {
var onSuccessListener: ((String) -> Unit)? = null
//Shaking margin value in the specified time
private val shakeTime: Array by lazy {
arrayOf(0F, 0.2F, 0.4F, 0.6F, 0.8F, 1.0F)
}
private val shakeMargin: Array by lazy {
arrayOf(30F, -30F, 15F, -15F, 8F, 0F)
}
init {
context.getLayoutInflater().inflate(R.layout.view_lock_keypad, this, true)
button_one.setOnClickListener { appendNumber("1") }
button_two.setOnClickListener { appendNumber("2") }
button_three.setOnClickListener { appendNumber("3") }
button_four.setOnClickListener { appendNumber("4") }
button_five.setOnClickListener { appendNumber("5") }
button_six.setOnClickListener { appendNumber("6") }
button_seven.setOnClickListener { appendNumber("7") }
button_eight.setOnClickListener { appendNumber("8") }
button_nine.setOnClickListener { appendNumber("9") }
button_zero.setOnClickListener { appendNumber("0") }
var isLongClick = false
val runnable = object : Runnable {
override fun run() {
isLongClick = true
input.removeLast()
handler.postDelayed(
this,
50L
)
}
}
button_delete.setOnTouchListener { _, event ->
hideError()
if (visibility == View.INVISIBLE) {
false
}
when (event.action) {
MotionEvent.ACTION_DOWN -> {
handler.postDelayed(
runnable,
android.view.ViewConfiguration.getLongPressTimeout().toLong()
)
}
MotionEvent.ACTION_UP -> {
handler.removeCallbacks(runnable)
if (!isLongClick) {
input.removeLast()
}
isLongClick = false
}
}
true
}
button_enter.setOnClickListener {
onSuccessListener?.let { it1 -> it1(input.text.toString()) }
}
clear_text.setOnClickListener {
input.text.clear()
hideError()
}
input.addTextChangedListener {
if (it.isNullOrEmpty()) {
clear_text.hide()
button_delete.invisible()
button_enter.isEnabled = false
} else {
clear_text.show()
button_delete.show()
button_enter.isEnabled = true
}
}
input.transformationMethod = object : TransformationMethod {
override fun onFocusChanged(
view: View?,
sourceText: CharSequence?,
focused: Boolean,
direction: Int,
previouslyFocusedRect: Rect?
) {
}
override fun getTransformation(source: CharSequence, view: View?): CharSequence {
return AsteriskPassword(source)
}
}
}
/**
* Convert password to * display
*/
class AsteriskPassword : CharSequence {
private val mSource: CharSequence
constructor(mSource: CharSequence) {
this.mSource = mSource
}
override val length: Int
get() = mSource.length
override fun get(index: Int): Char {
return '*'
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return mSource.subSequence(startIndex, endIndex); // Return default
}
}
fun showError() {
error_label.show()
shakeKeyframe(input, 400L).start()
}
fun hideError() {
error_label.hide()
}
private fun appendNumber(num: String) {
hideError()
input.text.append(num)
input.playSoundEffect(SoundEffectConstants.CLICK) // 为此视图播放声音效果;该框架将为某些内置动作(例如单击)播放声音效果
}
/**
* Shake left and right within a specified time
* @param view
* @param duration
* @return
*/
private fun shakeKeyframe(view: View, duration: Long): ObjectAnimator {
val pvhTranslateX = PropertyValuesHolder.ofKeyframe(
View.TRANSLATION_X,
Keyframe.ofFloat(shakeTime[0], shakeMargin[0]),
Keyframe.ofFloat(shakeTime[1], shakeMargin[1]),
Keyframe.ofFloat(shakeTime[2], shakeMargin[2]),
Keyframe.ofFloat(shakeTime[3], shakeMargin[3]),
Keyframe.ofFloat(shakeTime[4], shakeMargin[4]),
Keyframe.ofFloat(shakeTime[5], shakeMargin[5])
)
return ObjectAnimator.ofPropertyValuesHolder(view, pvhTranslateX).setDuration(duration)
}
}
Layout:view_lock_keypad(自定义view对应的布局文件)
2. XML使用示例:
3. Activity使用示例:
lock_keypad.onSuccessListener = { pin ->
lifecycleScope.launch {
try {
val user = userViewModel.getUserByPin(pin.toInt())
if (user != null) { //verify Success
lock_keypad.hideError()
sp.userId = user.userId
sp.userName = user.userName
lock_keypad.input.setText("")
//TODO 验证通过执行跳转页面
// findNavigate()
} else {
//TODO 验证不通过执行
lock_keypad.showError()
}
} catch (e: Exception) {
lock_keypad.showError()
Logger.e(TAG, "Error getting user from the db", e)
}
}
}