【CustomView】数字解锁键盘(LockKeypad)-附带错误抖动动画实现

自定义数字解锁功能,如下所示:

【CustomView】数字解锁键盘(LockKeypad)-附带错误抖动动画实现_第1张图片【CustomView】数字解锁键盘(LockKeypad)-附带错误抖动动画实现_第2张图片【CustomView】数字解锁键盘(LockKeypad)-附带错误抖动动画实现_第3张图片

#### 自定义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)
                }
            }
        }

 

 

 

你可能感兴趣的:(#,Custom-view,Custom,LockKeypad,transformation,shakeAnim)