Android自定义控件:通用验证码输入框

作者: Android技术分享 | 来源:发表于2018-03-14 18:16 被阅读781次

    关于自定义控件的基础知识
    -- 可自行查询资料。
    -- 也可关注我们,后期介绍自定义统计图表时,会统一介绍自定义控件的常用知识。


    需求

    4位验证码输入框:
    效果图:


    4位验证码输入框.gif
    1. 输入框一行可输入4位数字类型的验证码;
    2. 4位数字之间有间隔(包括底线);
    3. 输入框不允许有光标;
    4. 底线根据输入位置显示高亮(蓝色);
    6. 输入完成,回调结果,输入过程中,也进行回调;
    

    分析

    这种效果,很难直接在Edittext上处理:
    -- 输入框均分4等份,还要有间隔;
    -- 更难处理的是Edittext输入框禁止光标,那么,没有光标,我们如何调起虚拟键盘输入数据?
    -- 等...

    与其在一个控件上折腾,这么难受,不如自定义一个控件,实现这种效果。
    自定义控件最简单的方案:使用多个控件,组合出这种效果。

    1. 布局如何实现?
    1.禁止光标,我们直接使用TextView就解决了,而非Edittext;
    2.一行显示4位数字,比较简单,可以使用线性布局的权重,对TextView进行控制为4等分;
    3.每个TextView下面跟着一个底线,将来我们就能对底线设置高亮颜色了;
    

    这样,基本的布局展示就可以了!!!

    1. 使用了TextView,那么我们如何接收用户的输入呢?
    也很简单,我们在4个TextView的上方平铺一个EditText,设置透明,
    当用户点击到该控件时,会自动调起软键盘,接收输入的文本。
    
    1. EditText接收到用户输入的文本,如何显示在TextView呢?
    我们监听EditText文本输入事件,最多仅接收4个输入字符,
    每接收到一个字符,我们就赋值给对应的TextView;
    底线也随要设置的文本切换显示高亮;
    
    1. 如何删除已输入的数值?
    我们监听EditText按键事件,拦截DEL键,从后向前挨着删除字符即可;
    底线也随要删除的文本切换显示高亮;
    
    1. 是否需要自定义属性
    分析我们自己的项目,虽然是公用的控件,但是该控件比较简单,没有特别的要求,所以没必要自定义属性了!
    如果大家有需要的,可根据需要自己定义;
    如何定义属性?请自行查找资料;
    

    既然,问题都分析清楚了,那我们就开始快速实现吧


    具体实现

    1. 布局文件 phone_code.xml
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:id="@+id/ll_code"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical"
                android:layout_marginRight="7dp">
                <TextView
                    android:id="@+id/tv_code1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textColor="#2D2D2D"
                    android:textSize="40sp"
                    android:background="@null"
                    android:gravity="center"/>
                <View
                    android:id="@+id/v1"
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="#3F8EED" />
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical"
                android:layout_marginRight="7dp"
                android:layout_marginLeft="7dp">
                <TextView
                    android:id="@+id/tv_code2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textColor="#2D2D2D"
                    android:textSize="40sp"
                    android:background="@null"
                    android:gravity="center"/>
                <View
                    android:id="@+id/v2"
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="#999999" />
            </LinearLayout>
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical"
                android:layout_marginRight="7dp"
                android:layout_marginLeft="7dp">
                <TextView
                    android:id="@+id/tv_code3"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textColor="#2D2D2D"
                    android:textSize="40sp"
                    android:background="@null"
                    android:gravity="center"/>
                <View
                    android:id="@+id/v3"
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="#999999" />
            </LinearLayout>
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical"
                android:layout_marginLeft="7dp">
                <TextView
                    android:id="@+id/tv_code4"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textColor="#2D2D2D"
                    android:background="@null"
                    android:textSize="40sp"
                    android:gravity="center"/>
                <View
                    android:id="@+id/v4"
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="#999999" />
            </LinearLayout>
        </LinearLayout>
    
        <EditText
            android:id="@+id/et_code"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignTop="@+id/ll_code"
            android:layout_alignBottom="@+id/ll_code"
            android:background="@android:color/transparent"
            android:textColor="@android:color/transparent"
            android:cursorVisible="false"
            android:inputType="number"/>
    </RelativeLayout>
    

    et_code 输入框,设置了透明和无光标,仅接收数字;
    tv_code1~4 为显示数字的控件;
    v1~4 为数字文本的底线,用于设置高亮;

    1. 自定义控件代码 PhoneCode
    package iwangzhe.customview2.phonecode;
    
    import android.content.Context;
    import android.graphics.Color;
    import android.text.Editable;
    import android.text.TextWatcher;
    import android.util.AttributeSet;
    import android.view.KeyEvent;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.inputmethod.InputMethodManager;
    import android.widget.EditText;
    import android.widget.RelativeLayout;
    import android.widget.TextView;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import iwangzhe.customview2.R;
    
    /**
     * 类:PhoneCode
     * 作者: qxc
     * 日期:2018/3/14.
     */
    public class PhoneCode extends RelativeLayout {
        private Context context;
        private TextView tv_code1;
        private TextView tv_code2;
        private TextView tv_code3;
        private TextView tv_code4;
        private View v1;
        private View v2;
        private View v3;
        private View v4;
        private EditText et_code;
        private List<String> codes = new ArrayList<>();
        private InputMethodManager imm;
    
        public PhoneCode(Context context) {
            super(context);
            this.context = context;
            loadView();
        }
    
        public PhoneCode(Context context, AttributeSet attrs) {
            super(context, attrs);
            this.context = context;
            loadView();
        }
    
        private void loadView(){
            imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
            View view = LayoutInflater.from(context).inflate(R.layout.phone_code, this);
            initView(view);
            initEvent();
        }
    
        private void initView(View view){
            tv_code1 = (TextView) view.findViewById(R.id.tv_code1);
            tv_code2 = (TextView) view.findViewById(R.id.tv_code2);
            tv_code3 = (TextView) view.findViewById(R.id.tv_code3);
            tv_code4 = (TextView) view.findViewById(R.id.tv_code4);
            et_code = (EditText) view.findViewById(R.id.et_code);
            v1 = view.findViewById(R.id.v1);
            v2 = view.findViewById(R.id.v2);
            v3 = view.findViewById(R.id.v3);
            v4 = view.findViewById(R.id.v4);
        }
    
        private void initEvent(){
            //验证码输入
            et_code.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) {
                    if(editable != null && editable.length()>0) {
                        et_code.setText("");
                        if(codes.size() < 4){
                            codes.add(editable.toString());
                            showCode();
                        }
                    }
                }
            });
            // 监听验证码删除按键
            et_code.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
                    if (keyCode == KeyEvent.KEYCODE_DEL && keyEvent.getAction() == KeyEvent.ACTION_DOWN && codes.size()>0) {
                        codes.remove(codes.size()-1);
                        showCode();
                        return true;
                    }
                    return false;
                }
            });
        }
    
        /**
         * 显示输入的验证码
         */
        private void showCode(){
            String code1 = "";
            String code2 = "";
            String code3 = "";
            String code4 = "";
            if(codes.size()>=1){
                code1 = codes.get(0);
            }
            if(codes.size()>=2){
                code2 = codes.get(1);
            }
            if(codes.size()>=3){
                code3 = codes.get(2);
            }
            if(codes.size()>=4){
                code4 = codes.get(3);
            }
            tv_code1.setText(code1);
            tv_code2.setText(code2);
            tv_code3.setText(code3);
            tv_code4.setText(code4);        
            
            setColor();//设置高亮颜色
            callBack();//回调
        }
    
        /**
         * 设置高亮颜色
         */
        private void setColor(){
            int color_default = Color.parseColor("#999999");
            int color_focus = Color.parseColor("#3F8EED");
            v1.setBackgroundColor(color_default);
            v2.setBackgroundColor(color_default);
            v3.setBackgroundColor(color_default);
            v4.setBackgroundColor(color_default);
            if(codes.size()==0){
                v1.setBackgroundColor(color_focus);
            }
            if(codes.size()==1){
                v2.setBackgroundColor(color_focus);
            }
            if(codes.size()==2){
                v3.setBackgroundColor(color_focus);
            }
            if(codes.size()>=3){
                v4.setBackgroundColor(color_focus);
            }
        }
    
        /**
         * 回调
         */
        private void callBack(){
            if(onInputListener==null){
                return;
            }
            if(codes.size()==4){
                onInputListener.onSucess(getPhoneCode());
            }else{
                onInputListener.onInput();
            }
        }
    
        //定义回调
        public interface OnInputListener{
            void onSucess(String code);
            void onInput();
        }
        private OnInputListener onInputListener;
        public void setOnInputListener(OnInputListener onInputListener){
            this.onInputListener = onInputListener;
        }
    
        /**
         * 显示键盘
         */
        public void showSoftInput(){
            //显示软键盘
            if(imm!=null && et_code!=null) {
                et_code.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        imm.showSoftInput(et_code, 0);
                    }
                },200);
            }
        }
    
        /**
         * 获得手机号验证码
         * @return 验证码
         */
        public String getPhoneCode(){
            StringBuilder sb = new StringBuilder();
            for (String code : codes) {
                sb.append(code);
            }
            return sb.toString();
        }
    }
    

    codes 集合,用于存放用户输入的所有数字。使用该集合,可简化输入框、文本关联逻辑和事件之间处理;
    showSoftInput方法:显示输入键盘,可被外界调用;
    getPhoneCode方法:获得用户输入的验证码,可被外界调用;
    OnInputListener接口:定义的数值输入回调,用于告诉调用者是输入中,还是输入完成;
    (OnInputListener用途举例:在实际项目中,当输入完成,底部【下一步】或【确定】按钮变成可点击,否则,变为不可点击。)

    1. 调用者 MainActivity
      布局文件
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="iwangzhe.customview2.MainActivity">
        <iwangzhe.customview2.phonecode.PhoneCode
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/pc_1"
            android:layout_below="@+id/fpc_1"
            android:layout_marginTop="40dp"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"/>
    </RelativeLayout>
    

    代码

    package iwangzhe.customview2;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import iwangzhe.customview2.phonecode.PhoneCode;
    public class MainActivity extends AppCompatActivity {
        PhoneCode pc_1;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            pc_1 = (PhoneCode) findViewById(R.id.pc_1);
            //注册事件回调(根据实际需要,可写,可不写)
            pc_1.setOnInputListener(new PhoneCode.OnInputListener() {
                @Override
                public void onSucess(String code) {
                    //TODO: 例如底部【下一步】按钮可点击
                }
    
                @Override
                public void onInput() {
                    //TODO:例如底部【下一步】按钮不可点击
                }
            });
        }
    
        private void test(){
            //获得验证码
            String phoneCode = pc_1.getPhoneCode();
            //......
            //......
            //更多操作
        }
    }
    
    

    总结:
    此控件实现起来,很简单,代码量也非常少。
    本文章,主要是为了让大家了解自定义控件的过程,如果想在自己的项目中使用,请根据需要自行调整优化。

    Demo下载地址:
    (为了减小Demo大小,我删除了build下的文件,大家获取后rebuild一下代码,就可以了)
    https://pan.baidu.com/s/1OAcUdmwC_wFOrkontveAeA

    相关文章

      网友评论

      本文标题:Android自定义控件:通用验证码输入框

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