Android自定义验证码/密码输入框(输入框样式完全由自定义背景决定),高度复用性

工作中经常会用到验证码输入框,但是网上好多都是用4个TextView和一个隐藏的EditText,这样复用性不是很好。万幸找到了一个自定义View的案例,可以说是用很方便,也有很好的复用性,也是一个学习自定义View的不错的例子,通俗易懂,所以在这里分享一下。首先感谢作者Android 自定义验证码/密码输入框,验证码的样式完全由自定义Drawable决定

首先看一下效果


下面逐步说一下思路:

  1. 定义自定义属性:这里自定义了itemCount,itemWidth,itemBackground,gapWidth,ciTextSize,ciTextColor
  2. 完成测量
    1. 这里只需要考虑测量宽,高有xml指定具体值。
    2. 当为wrap_content时,需要自行测量控件总宽度
    3. 为match_parent或者具体指时,需要根据测量值重新计算itemWidth,这一点可以有不同实现
  3. 完成绘制
    1. 绘制背景和绘制字体的坐标计算还是需要理解的
    2. 绘制背景:自定义背景xml使用了focuse表示当前获取焦点的item,所以这里需要设置item的状态来绘制不同的背景
    3. 绘制字体—绘制字体居中在item的区域主要有两个关键:
      • 设置画笔textPaint.setTextAlign(Paint.Align.CENTER)这样方便找画字体的x坐标
      • 计算baseLine,参考了《Android自定义开发控件开发与入门实战》中的片段
  4. 软键盘的使用:
    1. 控件能够弹出软键盘需要setFocusableInTouchMode(true)和在事件处理,在onTouchEvent中判断按下事件弹出软键盘
    2. 软键盘的控制
      1. 重写onCreateInputConnection来控制弹出的软键盘的类型,类似于EditText的inputType并重写onCheckIsTextEditor
      2. 监听键盘按键事件,这里重写了onKeyDown这样不知道有没有问题,我这边自行测试没啥问题
      3. 在监听键盘按键事件中,要计算按下的是什么,设置监听回调,并重绘。

部分代码

  1. 测量

    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int mode = MeasureSpec.getMode(widthMeasureSpec);
         switch (mode) {
             case MeasureSpec.UNSPECIFIED:
             case MeasureSpec.AT_MOST:
                 //没有指定宽度 宽度等于单个的宽度*个数+总间距
                 mWidth = itemWidth * itemCount + gapWidth * (itemCount - 1);
                 break;
             case MeasureSpec.EXACTLY:
                 mWidth = MeasureSpec.getSize(widthMeasureSpec);
                 itemWidth = mWidth - (gapWidth * (itemCount - 1)) / itemCount;
                 break;
         }
         mHeight = MeasureSpec.getSize(heightMeasureSpec);
         calculateStartAndEndPoint(itemCount);
         setMeasuredDimension(mWidth, mHeight);
     }
    
  2. 绘制

    private void drawLine(Canvas canvas) {
        if (codeBuilder == null)
            return;
        int inputLength = codeBuilder.length();
        //画背景
        for (int i = 0; i < itemCount; i++) {
            if (itemBackground != null) {
                //绘制背景区域
                itemBackground.setBounds((int) itemPoints[i].x, 0, (int) itemPoints[i].y, mHeight);
                //关键点4:设置背景的状态,索引等于字符串长度,代表当前item处于FOCUSED状态,否则处于普通状态
                //这样可以使用xml文件Selector来的定义不同状态下的背景
                itemBackground.setState(i == inputLength ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
                itemBackground.draw(canvas);//把背景画上去
            }
        }
        //画字体
        Paint.FontMetricsInt fontMetricsInt = textPaint.getFontMetricsInt();
        //关键点5:基线的y坐标(固定套路)
        int baseline = mHeight / 2 + (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        for (int i = 0; i < inputLength; i++) {
            if (inputType == InputMode.INPUT_TYPE_NUMBER_PASSWORD || inputType == InputMode.NPUT_TYPE_PASSWORD) {
                canvas.drawCircle(itemPoints[i].y - itemWidth / 2, mHeight / 2, testSize / 4, textPaint);
            } else {
                canvas.drawText(codeBuilder.toString(), i, i + 1, itemPoints[i].y - itemWidth / 2, baseline, textPaint);
            }
        }
    
  3. 弹出软键盘

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            //            requestFocus();//关键点6:只有请求获取焦点,才会弹出软键盘
            KeyBoardUtil.showKeyboard(getContext(), this);
            return true;
        }
        return false;
    }
    

    4.定义键盘类型

    /**
     * //关键点7:定义软键盘类型
     * 创建一个新的InputConnection以便当前视图可以使用InputMethod,此方法默认实现返回null,
     * 因此它不支持输入法。因此如果当前视图想要使用输入法,则必须重写此方法,只有具有焦点和需要文本输入的视图
     * 才需要主动调用此方法
     * Create a new InputConnection for an InputMethod to interact
     * with the view.  The default implementation returns null, since it doesn't
     * support input methods.  You can override this to implement such support.
     * This is only needed for views that take focus and text input.
     * <p>
     * 当实现此方法后,最好也重写onCheckIsTextEditor方法来表明你将返回一个非空的InputConnection
     * <p>When implementing this, you probably also want to implement
     * {@link #onCheckIsTextEditor()} to indicate you will return a
     * non-null InputConnection.</p>
     * <p>
     * 另外,你必须指定EditorInfo的一些参数,以便系统输入法引擎可以参考这些参数来决定软键盘的信息
     * <p>Also, take good care to fill in the {@link android.view.inputmethod.EditorInfo}
     * object correctly and in its entirety, so that the connected IME(Input Method Engine) can rely
     * on its values. For example, {@link android.view.inputmethod.EditorInfo#initialSelStart}
     * and  {@link android.view.inputmethod.EditorInfo#initialSelEnd} members
     * must be filled in with the correct cursor position for IMEs to work correctly
     * with your application.</p>
     * <p>
     * 配置有关连接的属性信息
     *
     * @param outAttrs Fill in with attribute information about the connection.
     */
    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        BaseInputConnection bic = new BaseInputConnection(this, false);
        outAttrs.actionLabel = null;
        if (inputType == InputMode.INPUT_TYPE_NUMBER) {
            outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;
        } else if (inputType == InputMode.INPUT_TYPE_TEXT) {
            outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
        } else if (inputType == InputMode.INPUT_TYPE_TEXT_CAP_CHARACTERS) {
            outAttrs.inputType = InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
        } else if (inputType == InputMode.NPUT_TYPE_PASSWORD) {
            outAttrs.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD;
        } else if (inputType == InputMode.INPUT_TYPE_NUMBER_PASSWORD) {
            outAttrs.inputType = InputType.TYPE_NUMBER_VARIATION_PASSWORD;
        }
        outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
        return bic;
    }
    
  4. 处理键盘按键监听

     /**
      * 关键点9 后续研究监听软键盘的更好解决方案
      * 键盘回调函数,默认实现了ENTER键的动作
      * Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)
      * KeyEvent.Callback.onKeyDown()}: perform press of the view
      * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
      * is released, if the view is enabled and clickable.
      * 按压软键盘的按键通常不会触发此监听,尽管某些情况下有些人会选择实现此方法监听软键盘的键盘监听
      * 但是不要依靠此来监听软键盘按键
      * Key presses in software keyboards will generally NOT trigger this
      * listener, although some may elect to do so in some situations. Do not
      * rely on this to catch software key presses.
      *
      * @param keyCode a key code that represents the button pressed, from
      *                {@link android.view.KeyEvent}
      * @param event   the KeyEvent object that defines the button action
      */
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (codeBuilder == null)
             codeBuilder = new StringBuilder();
         if (keyCode == KeyEvent.KEYCODE_DEL) {
             //删除
             deleteLast();
         } else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
             //纯数字
             appendText(String.valueOf(event.getNumber()));
         } else if (((inputType == InputMode.INPUT_TYPE_TEXT || inputType == InputMode.INPUT_TYPE_TEXT_CAP_CHARACTERS || inputType == InputMode.NPUT_TYPE_PASSWORD))
                 && keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
             String text = String.valueOf((char) event.getUnicodeChar());
             appendText(inputType == InputMode.INPUT_TYPE_TEXT_CAP_CHARACTERS ? text.toUpperCase() : text);
         }
         if (codeBuilder.length() >= itemCount || keyCode == KeyEvent.KEYCODE_ENTER) {
             KeyBoardUtil.hideKeyboard(getContext(), this);
         }
         return super.onKeyDown(keyCode, event);
     }
    

    完整代码见github

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值