欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 自定义View之带密码模式的正方形验证码输入框

程序员文章站 2022-05-29 22:19:52
...

友情提示 该项目已在github开源,解决所有bug,也有更新计划,具体请看 README

最新版本已支持密码模式

概述:先描述一下具体需求吧,我们在项目中可能会遇到修改用户名及密码的需求,为保证一定的完全性,服务端一般会接入短信验证码的功能。我们需要将接受到的验证码返回给服务端进行验证。可能会有以下的界面让用户输入验证码:

Android 自定义View之带密码模式的正方形验证码输入框

那么此时我们就需要用自定义控件去实现我们的需求了。源码下载

一 分析

在安卓开发中,实现自定义控件的方式有三种,继承控件,组合控件,自绘控件。现在我们需要通过分析决定使用哪种方式。首先我们需要有输入事件,那么我们肯定是需要有EditText控件的。其次我们用需要显示验证码。我们需要显示文本的控件TextView。那么很明显了,我们需要使用组合控件的方式来实现我们的需求。

二 思路

我们需要通过EditText控件接受我们的输入,界面中明显是不能看到这个控件的,所以我们需要将这个控件给设置透明。然后将接受到的输入内容设置给我们的TextView。为了满足正方形的样式,只需要给TextView设置一个背景即可。在开发过程中,我们发现验证码的个数不是固定的,有4位数的,也有6位数的。为了实现低耦合的复用性。我们需要一个自定义的属性来满足这个要求。其他具体的思路将会在具体实现的时候讲。

三 实现

(1)组合控件的布局实现

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/container_et"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:showDividers="middle">

    </LinearLayout>

    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/transparent"
        android:inputType="number" />

</RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

这的LinearLayout用来存放TextVeiw ,android:showDividers=”middle”来设定我们的间隔的方式。

(2)设计自定义属性

    <!-- 自定义验证码输入框-->
    <declare-styleable name="IdentifyingCodeView">
        <!--输入框的数量-->
        <attr name="icv_et_number" format="integer" />
        <!--输入框的宽度-->
        <attr name="icv_et_width" format="dimension|reference" />
        <!--输入框之间的分割线-->
        <attr name="icv_et_divider_drawable" format="reference" />
        <!--输入框文字颜色-->
        <attr name="icv_et_text_color" format="color|reference" />
        <!--输入框文字大小-->
        <attr name="icv_et_text_size" format="dimension|reference" />
        <!--输入框获取焦点时边框-->
        <attr name="icv_et_bg_focus" format="reference" />
        <!--输入框没有焦点时边框-->
        <attr name="icv_et_bg_normal" format="reference" />
    </declare-styleable>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注释已经很清楚了,就不再解释了。

(3)获取自定义属性

    public class IdentifyingCodeView extends RelativeLayout

    public IdentifyingCodeView(Context context) {
        this(context, null);
    }

    public IdentifyingCodeView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public IdentifyingCodeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }


    //初始化 布局和属性
    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        LayoutInflater.from(context).inflate(R.layout.layout_identifying_code, this);
        containerEt = (LinearLayout) this.findViewById(R.id.container_et);
        et = (EditText) this.findViewById(R.id.et);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IdentifyingCodeView, defStyleAttr, 0);
        mEtNumber = typedArray.getInteger(R.styleable.IdentifyingCodeView_icv_et_number, 1);
        count = mEtNumber;
        mEtWidth = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_width, 42);
        mEtDividerDrawable = typedArray.getDrawable(R.styleable.IdentifyingCodeView_icv_et_divider_drawable);
        mEtTextSize = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_text_size, 16);
        mEtTextColor = typedArray.getColor(R.styleable.IdentifyingCodeView_icv_et_text_color, Color.WHITE);
        mEtBackgroundDrawableFocus = typedArray.getDrawable(R.styleable.IdentifyingCodeView_icv_et_bg_focus);
        mEtBackgroundDrawableNormal = typedArray.getDrawable(R.styleable.IdentifyingCodeView_icv_et_bg_normal);
        //释放资源
        typedArray.recycle();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

(4)在 onFinishInflate 方法中进行逻辑实现

① 初始化 TextView

initTextViews(getContext(), mEtNumber, mEtWidth, mEtDividerDrawable, mEtTextSize, mEtTextColor);

private void initTextViews(Context context, int etNumber, int etWidth, Drawable etDividerDrawable, float etTextSize, int etTextColor) {
        // 设置 editText 的输入长度
        //将光标隐藏
        et.setCursorVisible(false);
        //最大输入长度
        et.setFilters(new InputFilter[]{new InputFilter.LengthFilter(etNumber)}); 
        // 设置分割线的宽度
        if (etDividerDrawable != null) {
            etDividerDrawable.setBounds(0, 0, etDividerDrawable.getMinimumWidth(), etDividerDrawable.getMinimumHeight());
            containerEt.setDividerDrawable(etDividerDrawable);
        } 
        mTextViews = new TextView[etNumber];
        for (int i = 0; i < mTextViews.length; i++) {
            TextView textView = new EditText(context);
            textView.setTextSize(etTextSize);
            textView.setTextColor(etTextColor);
            textView.setWidth(etWidth);
            textView.setHeight(etWidth);
            if (i == 0) {
                textView.setBackgroundDrawable(mEtBackgroundDrawableFocus);
            } else {
                textView.setBackgroundDrawable(mEtBackgroundDrawableNormal);
            }
            textView.setGravity(Gravity.CENTER);

            textView.setFocusable(false);

            mTextViews[i] = textView;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

这里动态生成TextView 具体实现 注释很清楚,需要注意一点的是 textView.setFocusable(false); 不然在页面中TextView会获取焦点有一个 光标。用一个数组来存放 TextView。

② 填充盛放TextView的 LinerLayout

initEtContainer(mTextViews);

//初始化存储TextView 的容器
private void initEtContainer(TextView[] mTextViews) {
    for (int i = 0; i < mTextViews.length; i++) {
       containerEt.addView(mTextViews[i]);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

③ 处理我们的输入事件

 setListener();

 private void setListener() {
        // 监听输入内容
        et.addTextChangedListener(new TextWatcher() {

            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void afterTextChanged(Editable editable) {
                String inputStr = editable.toString();
                if (inputStr != null && !inputStr.equals("")) {
                    setText(inputStr);
                    et.setText("");
                }
            }
        });

        // 监听删除按键
        et.setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
                    onKeyDelete();
                    return true;
                }
                return false;
            }
        });
 }

    // 给TextView 设置文字
    public void setText(String inputContent) {
        for (int i = 0; i < mTextViews.length; i++) {
            TextView tv = mTextViews[i];
            if (tv.getText().toString().trim().equals("")) {
                tv.setText(inputContent);
                // 添加输入完成的监听
                if (inputCompleteListener != null) {
                    inputCompleteListener.inputComplete();
                }
                tv.setBackgroundDrawable(mEtBackgroundDrawableNormal);
                if (i < mEtNumber - 1) {
                    mTextViews[i + 1].setBackgroundDrawable(mEtBackgroundDrawableFocus);
                }
                break;
            }
        }
    }


    // 监听删除
    public void onKeyDelete() {
        for (int i = mTextViews.length - 1; i >= 0; i--) {
            TextView tv = mTextViews[i];
            if (!tv.getText().toString().trim().equals("")) {
                tv.setText("");
                // 添加删除完成监听
                if (inputCompleteListener != null) {
                    inputCompleteListener.deleteContent();
                }
                tv.setBackgroundDrawable(mEtBackgroundDrawableFocus);
                if (i < mEtNumber - 1) {
                    mTextViews[i + 1].setBackgroundDrawable(mEtBackgroundDrawableNormal);
                }
                break;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

代码都很简洁明了 就不一一说明了

④ 提供给外界的方法

    /**
     * 获取输入文本
     *
     * @return
     */
    public String getTextContent() {
        StringBuffer buffer = new StringBuffer();
        for (TextView tv : mTextViews) {
            buffer.append(tv.getText().toString().trim());
        }
        return buffer.toString();
    }

    /**
     * 删除所有内容
     */
    public void clearAllText() {
        for (int i = 0; i < mTextViews.length; i++) {
            if (i == 0) {
                mTextViews[i].setBackgroundDrawable(mEtBackgroundDrawableFocus);
            } else {
                mTextViews[i].setBackgroundDrawable(mEtBackgroundDrawableNormal);
            }
            mTextViews[i].setText("");
        }
    }


    /**
     * 获取输入的位数
     *
     * @return
     */
    public int getTextCount() {
        return mEtNumber;
    }

    // 输入完成 和 删除成功 的监听
    private InputCompleteListener inputCompleteListener;

    public void setInputCompleteListener(InputCompleteListener inputCompleteListener) {
        this.inputCompleteListener = inputCompleteListener;
    }

    public interface InputCompleteListener {
        void inputComplete();

        void deleteContent();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

四 效果

Android 自定义View之带密码模式的正方形验证码输入框
Android 自定义View之带密码模式的正方形验证码输入框

是不是棒棒哒 ,其中在逻辑实现过程中,代码和思路都非常的简洁明了。源码下载 GitHub地址

转自:https://blog.csdn.net/qq_33553515/article/details/73344155