Android仿支付密码输入框

 具体代码地址:https://github.com/Wcuren/figure_password_layout

效果图如下:

主要思想:

1、自定义View,重写onDraw,onMeasure,这个自定义View用来显示单个输入的数字

2、自定义一个父布局继承LinearLayout用来容纳显示数字密码的自定义View

3、处理键盘输入的监听事件

下面是主要代码:

单个显示密码的自定义View

class NumberView: View{
    val TAG = "NumberView"
    val DEFAULT_SIZE = 40  //单个显示密码的自定义View的默认大小
    var mContext: Context = context
    var isInputState: Boolean = false
    var isShowRemindLine: Boolean = false
    var mDrawRemindLineState: Boolean = false
    var isDrawText: Boolean = false
    var mPaint: Paint = Paint()
    var mShowPassType: Int = 0
    var mPasswordText: String = ""
    var mInputStateColor: Int = 0
    var mNoInputStateColor: Int = 0
    var mInputTextColor: Int = 0
    var mRemindLineColor: Int = 0
    var mTextSize: Int = 0
    var mBoxLineSize: Int = 0
    constructor(context: Context, attrs : AttributeSet?) : super(context, attrs) {
        Log.d(TAG, "inited!")
    }

    /**
    * 画边框
    */
    fun drawInputBox(canvas: Canvas?) {
        mPaint.reset()
        if (isInputState) {
            mPaint.color = ContextCompat.getColor(mContext, mInputStateColor)
        } else {
            mPaint.color = ContextCompat.getColor(mContext, mNoInputStateColor)
        }
        mPaint.style = Paint.Style.STROKE
        mPaint.strokeWidth = mBoxLineSize.toFloat()
        mPaint.isAntiAlias = true
        val rect = RectF(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat())
        canvas?.drawRect(rect, mPaint)
    }
    
    /**
    * 画光标(根据需要)
    */
    fun drawRemindLine(canvas: Canvas?) {
        mPaint.reset()
        if (mDrawRemindLineState && isShowRemindLine) {
            val lineHeight = measuredHeight / 2
            mPaint.style = Paint.Style.FILL
            mPaint.color = ContextCompat.getColor(mContext, mRemindLineColor)
            canvas?.drawLine((measuredWidth / 2).toFloat(),
                    (measuredHeight / 2 - lineHeight / 2).toFloat(),
                    (measuredWidth / 2).toFloat(),
                    (measuredHeight / 2 + lineHeight / 2).toFloat(), mPaint)
        }
    }

    /**
    * 画密码,有三种选择,分别是:. 或者 * 或者直接显示数字
    */
    fun drawPassword(canvas: Canvas?) {
        if (isDrawText) {
            mPaint.reset()
            mPaint.color = ContextCompat.getColor(mContext, mInputTextColor)
            mPaint.style = Paint.Style.FILL
            mPaint.isAntiAlias = true
            when (mShowPassType) {
                0  // .
                -> canvas?.drawCircle((measuredWidth / 2).toFloat(), (measuredHeight / 2).toFloat(), (measuredWidth / 4).toFloat(), mPaint)
                1  // *
                -> {
                    mPaint.textSize = (measuredWidth / 2 + 10).toFloat()
                    val strWidth = mPaint.measureText("*")
                    val baseY = measuredHeight / 2 - (mPaint.descent() + mPaint.ascent()) / 2 + strWidth / 3
                    val baseX = measuredWidth / 2 - strWidth / 2
                    canvas?.drawText("*", baseX, baseY, mPaint)
                }
                2  //figure
                -> {
                    mPaint.textSize = mContext.resources.getDimensionPixelOffset(R.dimen.size_figure_pwd_text).toFloat()
                    val strWidth2 = mPaint.measureText(mPasswordText)
                    val baseY2 = measuredHeight / 2 - (mPaint.descent() + mPaint.ascent()) / 2 + strWidth2 / 5
                    val baseX2 = measuredWidth / 2 - strWidth2 / 2
                    canvas?.drawText(mPasswordText, baseX2, baseY2, mPaint)
                }
            }
        }
    }

    override fun onDraw(canvas: Canvas?) {
        Log.d(TAG, "onDraw!")
        super.onDraw(canvas)
        drawInputBox(canvas)
        drawRemindLine(canvas)
        drawPassword(canvas)
    }

    /**
     * 计算自定义view的宽高尺寸,通过MeasureSpec辅助计算,MeasureSpec是size和mode通过位运算得到的一个整型值,
     * 其中mode有三种值:UNSPECIFIED,EXACTLY,AT_MOST
     */
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val width = measureSize(widthMeasureSpec)
        val height = measureSize(heightMeasureSpec)
        setMeasuredDimension(width, height)
    }

    /**
     * UNSPECIFIED:表示父布局对子view不限制大小
     * EXACTLY:表示父view对子view的大小设置具体的值,一般layout_width和layout_height为match_parent或固定值时,这里的mode为EXACTLY
     * AT_MOST:表示父view对子view的大小限定不能超过某个最大值,一般layout_width和layout_height为wrap_content时,这里的mode为AT_MOST
     */
    fun measureSize(measureSpec: Int) : Int {
        val mode = MeasureSpec.getMode(measureSpec)
        val size = MeasureSpec.getSize(measureSpec)
        when (mode) {
            MeasureSpec.AT_MOST -> return DEFAULT_SIZE
            MeasureSpec.EXACTLY -> return size
            MeasureSpec.UNSPECIFIED -> return DEFAULT_SIZE
            else -> {
                return DEFAULT_SIZE
            }
        }
    }
}

父布局代码:

class NumberLayout: LinearLayout {

    val TAG = "NumberLayout"
    val DEFAULT_SIZE = 0
    var maxLength: Int = 0
    var mIsShowInputLine: Boolean = false
    var itemWidth: Int = 0
    var itemHeight: Int = 0
    var inputColor: Int = 0
    var noinputColor: Int = 0
    var lineColor: Int = 0
    var txtInputColor: Int = 0
    var interval: Int = 0
    var txtSize: Int = 0
    var boxLineSize: Int = 0
    var drawType: Int = 0
    var showPassType: Int = 0
    var mFigureCursor: Int = -1
    var mFigurePwdViews: MutableList<NumberView> = mutableListOf()

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        Log.d(TAG, "NumberLayout inited!")
        val types = context.obtainStyledAttributes(attrs, R.styleable.NumberLayoutStyle)
        inputColor = types.getResourceId(R.styleable.NumberLayoutStyle_box_input_color, R.color.colorPrimary)
        noinputColor = types.getResourceId(R.styleable.NumberLayoutStyle_box_no_input_color, R.color.text_color_99)
        lineColor = types.getResourceId(R.styleable.NumberLayoutStyle_input_line_color, R.color.text_color_99)
        txtInputColor = types.getResourceId(R.styleable.NumberLayoutStyle_text_input_color, R.color.black)
        drawType= types.getInt(R.styleable.NumberLayoutStyle_box_draw_type, 0)
        interval = types.getInt(R.styleable.NumberLayoutStyle_interval_width, 10)
        maxLength = types.getInt(R.styleable.NumberLayoutStyle_pass_leng, 6)
        itemWidth = types.getInt(R.styleable.NumberLayoutStyle_item_width, 40)
        itemHeight = types.getInt(R.styleable.NumberLayoutStyle_item_height, 40)
        showPassType = types.getInt(R.styleable.NumberLayoutStyle_pass_tips_type, 0)
        txtSize = types.getInt(R.styleable.NumberLayoutStyle_draw_txt_size, 18)
        boxLineSize = types.getInt(R.styleable.NumberLayoutStyle_draw_box_line_size, 4)
        mIsShowInputLine = types.getBoolean(R.styleable.NumberLayoutStyle_is_show_input_line, true)
        types.recycle()
        initView(context)
    }

    fun initView(context: Context) {
        mFigurePwdViews.clear()
        for (i in 0 until maxLength) {
            var view = NumberView(context,null)
            var lp = LayoutParams(DensityUtil.dp2px(context, itemWidth.toFloat()),
                    DensityUtil.dp2px(context, itemHeight.toFloat()))
            if (i != 0) {
                lp.setMarginStart(DensityUtil.dp2px(context, interval.toFloat()))
            }
            view.mInputStateColor = inputColor
            view.mNoInputStateColor = noinputColor
            view.mInputTextColor = txtInputColor
            view.mRemindLineColor = lineColor
            view.mShowPassType = showPassType
            view.mTextSize = txtSize
            view.mBoxLineSize = boxLineSize
            view.isShowRemindLine = mIsShowInputLine
            mFigurePwdViews.add(view)
            addView(view, lp)
        }
        setOnClickListener({layoutClicked()})
        setOnKeyListener(CustomFigureKeyListener())
    }

    /**
     * 点击弹出键盘
     */
    private fun layoutClicked() {
        setFocusable(true)
        setFocusableInTouchMode(true)
        requestFocus()
        val inputMethodManager: InputMethodManager =
                context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
    }

    /**
     * 这个函数的作用是建立view与输入法的联系
     */
    override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection {
        outAttrs?.inputType = InputType.TYPE_CLASS_NUMBER    //这一步是用来限定显示数字键盘
        outAttrs?.imeOptions =  EditorInfo.IME_FLAG_NO_EXTRACT_UI

        return CustomInputConnection(this, true)
    }

    fun delItemPassword() {
        if (mFigureCursor > -1) {
            mFigurePwdViews.get(mFigureCursor).isDrawText = false
            mFigurePwdViews.get(mFigureCursor).invalidate()
            mFigureCursor --
            mFigureChangeListener?.onDeleteFigure()
        }
    }

    fun setItemPassword(password: String) {
        if (mFigureCursor < (mFigurePwdViews.size - 1)) {
            mFigureCursor++
            mFigurePwdViews.get(mFigureCursor).isDrawText = true
            mFigurePwdViews.get(mFigureCursor).mPasswordText = password
            mFigurePwdViews.get(mFigureCursor).invalidate()
            mFigureChangeListener?.onAddFigure(password)
        }
    }

    /**
     * 定义这个内部类的原因是:有的输入法在onkeyListener中无法监听到Delete这个键,所以在发送这个键的地方做监听处理
     */
    inner class CustomInputConnection(targetView: View, fullEditor: Boolean):
            BaseInputConnection(targetView, fullEditor) {
        override fun sendKeyEvent(event: KeyEvent?): Boolean {
            if (event?.action == KeyEvent.ACTION_DOWN &&
                    event?.keyCode == KeyEvent.KEYCODE_DEL) {
                delItemPassword()
                return true
            }
            return super.sendKeyEvent(event)
        }

        override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
            if (mFigureCursor > -1 ){
                return sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                && sendKeyEvent(KeyEvent(KeyEvent.ACTION_UP,KeyEvent.KEYCODE_DEL))
            } else {
                return super.deleteSurroundingText(beforeLength, afterLength)
            }
        }
    }

    inner class CustomFigureKeyListener(): OnKeyListener {

        override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
            if (event?.action == KeyEvent.ACTION_DOWN) {
                if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
                    setItemPassword((keyCode - 7).toString())
                    return true
                } else if (keyCode == KeyEvent.KEYCODE_DEL) {
                    delItemPassword()
                    return true
                } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
                    mFigureChangeListener?.onEnter()
                    return true
                }
            }
            return false
        }
    }

    var mFigureChangeListener: FigureChangeListener ?= null

    interface FigureChangeListener {
        fun onAddFigure(figure: String)
        fun onDeleteFigure()
        fun onEnter()
    }
}

猜你喜欢

转载自blog.csdn.net/u010671781/article/details/81741624
今日推荐