美文网首页
Android 自定义验证码、密码输入框的控件实现

Android 自定义验证码、密码输入框的控件实现

作者: SongSenior | 来源:发表于2021-04-20 11:28 被阅读0次

    [TOC]

    Android 自定义验证码、密码输入框的控件实现

    效果如下

    Screenshot_2021-04-16-15-31-34-341_com.miui.video

    动图

    验证码控件

    基本思路

    image-20210420104943869

    一个横向布局的LinearLayout,里面包含一个1px 的 EditText 和 n 个 TextView ,监听 EditText 的输入字符,把字符设置到相应的 TextView 上。

    来人啊!上代码!!

    VerifyEditText.java

    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.drawable.Drawable;
    import android.text.Editable;
    import android.text.InputFilter;
    import android.text.InputType;
    import android.text.TextUtils;
    import android.text.TextWatcher;
    import android.text.method.HideReturnsTransformationMethod;
    import android.text.method.PasswordTransformationMethod;
    import android.util.AttributeSet;
    import android.view.Gravity;
    import android.view.ViewGroup;
    import android.view.inputmethod.InputMethodManager;
    import android.widget.EditText;
    import android.widget.LinearLayout;
    import android.widget.TextView;
    import androidx.annotation.Nullable;
    import androidx.core.content.ContextCompat;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by SongSenior on 2021/4/16
     * 一个 EditText 和 n 个 TextView
     */
    public class VerifyEditText extends LinearLayout {
        //默认 item 个数为 4 个
        private final static int DEFAULT_ITEM_COUNT = 4;
        //默认每个 item 的宽度为 100
        private final static int DEFAULT_ITEM_WIDTH = 100;
        //默认每个 item 的间距为 50
        private final static int DEFAULT_ITEM_MARGIN = 50;
        //默认每个 item 的字体大小为 14
        private final static int DEFAULT_ITEM_TEXT_SIZE = 14;
        //默认密码明文显示时间为 200ms,之后密文显示
        private final static int DEFAULT_PASSWORD_VISIBLE_TIME = 200;
    
        private final List<TextView> mTextViewList = new ArrayList<>();
        private EditText mEditText;
        private Drawable drawableNormal, drawableSelected;
        private Context mContext;
        //输入完成监听
        private InputCompleteListener mInputCompleteListener;
    
        public VerifyEditText(Context context) {
            this(context, null);
        }
    
        public VerifyEditText(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public VerifyEditText(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context, attrs);
        }
    
        private void init(Context context, @Nullable AttributeSet attrs) {
            mContext = context;
            setOrientation(HORIZONTAL);
            setGravity(Gravity.CENTER);
            @SuppressLint("CustomViewStyleable") TypedArray obtainStyledAttributes =
                    getContext().obtainStyledAttributes(attrs, R.styleable.verify_EditText);
            drawableNormal = obtainStyledAttributes.getDrawable(R.styleable.verify_EditText_verify_background_normal);
            drawableSelected = obtainStyledAttributes.getDrawable(R.styleable.verify_EditText_verify_background_selected);
            int textColor = obtainStyledAttributes.getColor(R.styleable.verify_EditText_verify_textColor,
                    ContextCompat.getColor(context, android.R.color.black));
            int count = obtainStyledAttributes.getInt(R.styleable.verify_EditText_verify_count, DEFAULT_ITEM_COUNT);
            int inputType = obtainStyledAttributes.getInt(R.styleable.verify_EditText_verify_inputType, InputType.TYPE_CLASS_NUMBER);
            int passwordVisibleTime = obtainStyledAttributes.getInt(R.styleable.verify_EditText_verify_password_visible_time, DEFAULT_PASSWORD_VISIBLE_TIME);
            int width = (int) obtainStyledAttributes.getDimension(R.styleable.verify_EditText_verify_width, DEFAULT_ITEM_WIDTH);
            int height = (int) obtainStyledAttributes.getDimension(R.styleable.verify_EditText_verify_height, 0);
            int margin = (int) obtainStyledAttributes.getDimension(R.styleable.verify_EditText_verify_margin, DEFAULT_ITEM_MARGIN);
            float textSize = px2sp(context,obtainStyledAttributes.getDimension(R.styleable.verify_EditText_verify_textSize, sp2px(context,DEFAULT_ITEM_TEXT_SIZE)));
            boolean password = obtainStyledAttributes.getBoolean(R.styleable.verify_EditText_verify_password, false);
            obtainStyledAttributes.recycle();
            if (count < 2) count = 2;//最少 2 个 item
    
            mEditText = new EditText(context);
            mEditText.setInputType(inputType);
            mEditText.setLayoutParams(new LinearLayout.LayoutParams(1, 1));
            mEditText.setCursorVisible(false);
            mEditText.setBackground(null);
            mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(count)});//限制输入长度为 count
            mEditText.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    
                }
    
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    TextView textView = mTextViewList.get(start);//获取对应的 textview
                    if (before == 0) {//输入
                        CharSequence input = s.subSequence(start, s.length());//获取新输入的字
                        textView.setText(input);
                        if (password) {//如果需要密文显示
                            textView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
                            //passwordVisibleTime 毫秒后设置为密文显示
                            textView.postDelayed(() ->
                                            textView.setTransformationMethod(PasswordTransformationMethod.getInstance()),
                                    passwordVisibleTime);
                        }
                        setTextViewBackground(textView, drawableSelected);
                    } else {//删除
                        textView.setText("");
                        setTextViewBackground(textView, drawableNormal);
                    }
                    if (mInputCompleteListener != null && s.length() == mTextViewList.size())
                        mInputCompleteListener.complete(s.toString());
                }
    
                @Override
                public void afterTextChanged(Editable s) {
    
                }
            });
            addView(mEditText);
            //点击弹出软键盘
            setOnClickListener(v -> {
                mEditText.requestFocus();
                showSoftKeyBoard();
            });
            //遍历生成 textview
            for (int i = 0; i < count; i++) {
                TextView textView = new TextView(context);
                textView.setTextSize(textSize);
                textView.setGravity(Gravity.CENTER);
                textView.setTextColor(textColor);
                LayoutParams layoutParams = new LayoutParams(width, height == 0 ? ViewGroup.LayoutParams.WRAP_CONTENT : height);
                if (i == 0)
                    layoutParams.leftMargin = -1;
                else
                    layoutParams.leftMargin = margin;
                textView.setLayoutParams(layoutParams);
                setTextViewBackground(textView, drawableNormal);
                addView(textView);
                mTextViewList.add(textView);
            }
        }
    
        /**
         * view 添加到窗口时,延迟 500ms 弹出软键盘
         */
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            mEditText.postDelayed(this::showSoftKeyBoard, 500);
        }
    
        /**
         * 设置背景
         * @param textView
         * @param drawable
         */
        private void setTextViewBackground(TextView textView, Drawable drawable) {
            if (drawable != null)
                textView.setBackground(drawable);
        }
    
        /**
         * 获取当前输入的内容
         *
         * @return
         */
        public String getContent() {
            Editable text = mEditText.getText();
            if (TextUtils.isEmpty(text)) return "";
            return mEditText.getText().toString();
        }
    
        /**
         * 清除内容
         */
        public void clearContent() {
            mEditText.setText("");
            for (int i = 0; i < mTextViewList.size(); i++) {
                TextView textView = mTextViewList.get(i);
                textView.setText("");
                setTextViewBackground(textView, drawableNormal);
            }
        }
    
        /**
         * 设置默认的内容
         *
         * @param content
         */
        public void setDefaultContent(String content) {
            mEditText.setText(content);
            mEditText.requestFocus();
            char[] chars = content.toCharArray();
            int min = Math.min(chars.length, mTextViewList.size());
            for (int i = 0; i < min; i++) {
                char aChar = chars[i];
                String s = String.valueOf(aChar);
                TextView textView = mTextViewList.get(i);
                textView.setText(s);
                setTextViewBackground(textView, drawableSelected);
            }
            if (mInputCompleteListener != null && min == mTextViewList.size())
                mInputCompleteListener.complete(content.substring(0, min));
    
        }
    
        /**
         * 显示软键盘
         */
        private void showSoftKeyBoard() {
            InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED);
        }
    
        /**
         * 添加输入完成的监听
         *
         * @param inputCompleteListener
         */
        public void addInputCompleteListener(InputCompleteListener inputCompleteListener) {
            mInputCompleteListener = inputCompleteListener;
            Editable content = mEditText.getText();
            if (!TextUtils.isEmpty(content) && content.toString().length() == mTextViewList.size()) {
                mInputCompleteListener.complete(content.toString());
            }
        }
    
        public interface InputCompleteListener {
            void complete(String content);
        }
    
        private int px2sp(Context context, float pxValue) {
            final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
            return (int) (pxValue / fontScale + 0.5f);
        }
    
        private int sp2px(Context context, float spValue) {
            final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;  
            return (int) (spValue * fontScale + 0.5f);  
        }
    }
    

    属性定义

    <resources xmlns:tools="http://schemas.android.com/tools">
        <declare-styleable name="verify_EditText">
            <!-- 验证码的个数 -->
            <attr name="verify_count" format="integer"/>
            <!--        TextView 的宽度-->
            <attr name="verify_width" format="dimension"/>
            <!--        TextView 的高度-->
            <attr name="verify_height" format="dimension"/>
            <!--        TextView 的间隔-->
            <attr name="verify_margin" format="dimension"/>
            <!--        TextView 的字体大小-->
            <attr name="verify_textSize" format="dimension"/>
            <!--        TextView 的字体颜色-->
            <attr name="verify_textColor" format="color"/>
            <!-- TextView 无值背景 -->
            <attr name="verify_background_normal" format="reference"/>
            <!-- TextView 有值背景 -->
            <attr name="verify_background_selected" format="reference"/>
            <!--        是否隐藏密码-->
            <attr name="verify_password" format="boolean"/>
            <!--        密码显示时间ms-->
            <attr name="verify_password_visible_time" format="integer"/>
            <!--        editText的输入类型 -->
            <attr name="verify_inputType">
                <flag name="none" value="0x00000000" />
                <flag name="text" value="0x00000001" />
                <flag name="textCapCharacters" value="0x00001001" />
                <flag name="textCapWords" value="0x00002001" />
                <flag name="textCapSentences" value="0x00004001" />
                <flag name="textAutoCorrect" value="0x00008001" />
                <flag name="textAutoComplete" value="0x00010001" />
                <flag name="textMultiLine" value="0x00020001" />
                <flag name="textImeMultiLine" value="0x00040001" />
                <flag name="textNoSuggestions" value="0x00080001" />
                <flag name="textUri" value="0x00000011" />
                <flag name="textEmailAddress" value="0x00000021" />
                <flag name="textEmailSubject" value="0x00000031" />
                <flag name="textShortMessage" value="0x00000041" />
                <flag name="textLongMessage" value="0x00000051" />
                <flag name="textPersonName" value="0x00000061" />
                <flag name="textPostalAddress" value="0x00000071" />
                <flag name="textPassword" value="0x00000081" />
                <flag name="textVisiblePassword" value="0x00000091" />
                <flag name="textWebEditText" value="0x000000a1" />
                <flag name="textFilter" value="0x000000b1" />
                <flag name="textPhonetic" value="0x000000c1" />
                <flag name="textWebEmailAddress" value="0x000000d1" />
                <flag name="textWebPassword" value="0x000000e1" />
                <flag name="number" value="0x00000002" />
                <flag name="numberSigned" value="0x00001002" />
                <flag name="numberDecimal" value="0x00002002" />
                <flag name="numberPassword" value="0x00000012" />
                <flag name="phone" value="0x00000003" />
                <flag name="datetime" value="0x00000004" />
                <flag name="date" value="0x00000014" />
                <flag name="time" value="0x00000024" />
            </attr>
        </declare-styleable>
    </resources>
    

    使用方法

    自定义属性

    app:verify_count = "5"//验证码item个数
    app:verify_width = "50dp"//单个 item 的高度
    app:verify_height = "50dp"//单个 item 的宽度
    app:verify_margin = "15dp"//item 间的间隔
    app:verify_textSize = "15sp"//item 的字体大小
    app:verify_textColor = "#ff00dd"//item 的文字颜色
    app:verify_background_normal = "@drawable/shape_bottom_line_normal"//空背景
    app:verify_background_selected = "@drawable/shape_bottom_line_selected"//输入值后的背景
    app:verify_password = "true"//显示密文 true,显示明文 false
    app:verify_password_visible_time = "200"//输入值200ms后变为密文显示
    app:verify_inputType = "none"//使用方式和 EditText 的 inputType 一样
    

    布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <com.songsenior.verifyedittext.VerifyEditText
            android:id="@+id/vet1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:verify_count = "4"
            android:layout_marginTop="30dp"
            app:verify_inputType = "number"
            app:verify_password = "true"
            app:verify_width = "20dp"
            app:verify_height = "20dp"
            app:verify_password_visible_time = "500"
            app:verify_textSize = "14sp"
            app:verify_margin = "25dp"
            app:verify_background_normal = "@drawable/shape_verify_edittext_default_bg"/>
    
        <com.songsenior.verifyedittext.VerifyEditText
            android:id="@+id/vet2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            app:verify_count = "5"
            app:verify_inputType = "text"
            app:verify_textSize = "20sp"
            app:verify_height = "30dp"
            app:verify_margin = "15dp"
            app:verify_background_normal = "@drawable/shape_bottom_line_normal"
            app:verify_background_selected = "@drawable/shape_bottom_line_selected"/>
    
        <com.songsenior.verifyedittext.VerifyEditText
            android:id="@+id/vet3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            app:verify_count = "6"
            app:verify_inputType = "number"
            app:verify_textSize = "25sp"
            app:verify_margin = "15dp"
            app:verify_background_normal = "@drawable/shape_verify_edittext_default_bg2"/>
    
    </LinearLayout>
    

    drawable

    shape_verify_edittext_default_bg.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <solid android:color="#A3A3A3"/>
        <corners android:radius="5dp"/>
    </shape>
    

    shape_bottom_line_normal.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- This is the line -->
        <item>
            <shape>
                <solid android:color="#A3A3A3" />
            </shape>
        </item>
        <item android:bottom="2dp">
            <shape>
                <solid android:color="#FFFFFFFF" />
            </shape>
        </item>
    </layer-list>
    

    shape_bottom_line_selected.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- This is the line -->
        <item>
            <shape>
                <solid android:color="#FF03DAC5" />
            </shape>
        </item>
        <item android:bottom="2dp">
            <shape>
                <solid android:color="#FFFFFFFF" />
            </shape>
        </item>
    </layer-list>
    

    shape_verify_edittext_default_bg2.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <stroke android:width="1dp" android:color="#A3A3A3"/>
        <corners android:radius="5dp"/>
    </shape>
    

    项目依赖

    1、在根 build.gradle上添加maven

    allprojects {
            repositories {
                ...
                maven { url 'https://jitpack.io' }
            }
        }
    

    2、添加依赖

    dependencies {
                implementation 'com.github.SongSenior:VerifyEditText:1.0'
        }
    

    最后附上 demo

    相关文章

      网友评论

          本文标题:Android 自定义验证码、密码输入框的控件实现

          本文链接:https://www.haomeiwen.com/subject/tvzglltx.html