美文网首页
倒计时发送验证码+验证码输入框

倒计时发送验证码+验证码输入框

作者: 沈溺_16e5 | 来源:发表于2019-05-11 18:51 被阅读0次
    这里用一个Activity实现登录界面和输入验证码界面(一个Activity中添加两个Fragment)

    Activity

    Activity的xml

    用帧布局来实现

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout 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"
        tools:context=".ui.login.LoginActivity"
        android:id="@+id/frame">
    
    </FrameLayout>
    
    Activity
    public class LoginActivity extends BaseActivity<LoginView, LoginPresenter> implements LoginView{
        public static int TYPE_LOGIN = 0;
        public static int TYPE_BIND = 1;
    
        @BindView(R.id.frame)
        FrameLayout frame;
        private int type;
        public static String TAG = "loginFragment";
    
        @Override
        protected LoginPresenter initPresenter() {
            return new LoginPresenter();
        }
    
        @Override
        protected int initLayoutId() {
            return R.layout.activity_login;
        }
    
        // 其他Activity到这个Activity掉这个方法就可以
        // context 其他Activity的上下文
        // type 上面有参数,TYPE_LOGIN 登录界面,TYPE_BIND  绑定界面,
        // 因为后面登录和绑定手机号用同一个界面,所以用type区分一下
        public static void startAct(Context context,int type){
            Intent intent = new Intent(context, LoginActivity.class);
            intent.putExtra(Constants.TYPE,type);
            context.startActivity(intent);
        }
    
        @Override
        protected void initView() {
            super.initView();
    
            // 一开始就显示登录界面
            addLoginFragment();
    
        }
    
        private void addLoginFragment() {
            FragmentManager manager = getSupportFragmentManager();
            FragmentTransaction transaction = manager.beginTransaction();
            getIntentData();
            LoginFragment loginFragment=LoginFragment.newInstance(type);
            // 给LoginFragment添加标记,后面在输入验证界
            // 面需要通过标记来获取LoginFragment的对象
            transaction.add(R.id.frame, loginFragment,TAG);
            transaction.addToBackStack(null);
            transaction.commit();
        }
    
        private void getIntentData() {
            type = getIntent().getIntExtra(Constants.TYPE, TYPE_LOGIN);
        }
    
        // 友盟第三方登录的内容
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            UMShareAPI.get(this).onActivityResult(requestCode, resultCode, data);
        }
    
        private long exitTime = 0;
    
        // 手机返回键的监听
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN){
                if((System.currentTimeMillis()-exitTime) > 2000){
                    ToastUtil.showShort("再按一次退出程序");
                    exitTime = System.currentTimeMillis();
                } else {
                    finish();
                    System.exit(0);
                }
                return true;
            }
            return super.onKeyDown(keyCode, event);
    }
    
        // 友盟第三方登录的内容
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //内存泄漏解决方案
            UMShareAPI.get(this).release();
        }
    }
    
    登录.gif

    LoginFragment

    主要是从onViewClicked()方法开始的
    public class LoginFragment extends BaseFragment<LoginFraView, LoginFraPresenter> implements LoginFraView {
    
        private static final String TAG = "LoginFragment";
    
        @BindView(R.id.phone_number)
        EditText phoneNumber;
        @BindView(R.id.send_code)
        Button sendCode;
        @BindView(R.id.wechat)
        ImageButton wechat;
        @BindView(R.id.qq)
        ImageButton qq;
        @BindView(R.id.sina)
        ImageButton sina;
        @BindView(R.id.user_agreement)
        TextView userAgreement;
        @BindView(R.id.back)
        ImageView back;
        @BindView(R.id.ll)
        LinearLayout ll;
        @BindView(R.id.ll_three)
        LinearLayout llThree;
        private EditText phone_number;
        private int type;
        private static int TIME = 20;
        private int time=TIME;
        String mCode;
        private Handler mHandler;
        private VerifyCodeNewFragment verifyCodeNewFragment;
    
        public LoginFragment() {
            // Required empty public constructor
        }
    
        // 其他界面通过这个方法可以获取到LoginFragment 的对象
        public static LoginFragment newInstance(int type) {
            LoginFragment loginFragment = new LoginFragment();
            Bundle bundle = new Bundle();
            bundle.putInt(Constants.TYPE, type);
            loginFragment.setArguments(bundle);
            return loginFragment;
        }
    
        @Override
        protected LoginFraPresenter initPresenter() {
            return new LoginFraPresenter();
        }
    
        @Override
        protected int initLayoutId() {
            return R.layout.fragment_login;
        }
    
        @Override
        protected void initView() {
            super.initView();
            // 设置 EditText 中 hint 的字体大小
            SpannableString spannableString = new SpannableString("请输入手机号");
            AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(16, true);
            spannableString.setSpan(sizeSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            phoneNumber.setHint(spannableString);
    
            getArgumentsData();
    
            setAgreement();
    
            // 登录和绑定手机号用同一个fragmnet
            // 所以显示或隐藏一些view
            showOrHideView();
    
        }
    
        private void showOrHideView() {
            if (type== LoginActivity.TYPE_LOGIN){
                // 登录
                back.setVisibility(View.INVISIBLE);
                ll.setVisibility(View.VISIBLE);
                llThree.setVisibility(View.VISIBLE);
            }else {
                // 绑定
                back.setVisibility(View.VISIBLE);
                ll.setVisibility(View.INVISIBLE);
                llThree.setVisibility(View.INVISIBLE);
            }
        }
    
        private void getArgumentsData() {
            Bundle arguments = getArguments();
            type = arguments.getInt(Constants.TYPE);
        }
    
        @Override
        protected void initListener() {
            super.initListener();
            // 监听EditText中的内容,符合手机号就改变“发送验证码”的背景图片
            phoneNumber.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) {
                    String string = s.toString();
                    if (string.matches("[1][34578][0-9]{9}")) {
                        sendCode.setBackgroundResource(R.drawable.shape_send_code_bright);
                    } else {
                        sendCode.setBackgroundResource(R.drawable.shape_send_code_dark);
                    }
                }
    
                @Override
                public void afterTextChanged(Editable s) {
    
                }
            });
        }
    
        // 给布局中的“用户协议”设置前景色和下划线
        private void setAgreement() {
            SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(getResources().getString(R.string.useragreement));
            //点击事件
            ClickableSpan clickableSpan = new ClickableSpan() {
                @Override
                public void onClick(View widget) {
                    //跳转页面,webview展示协议
                    //webView有很多坑,所以我们不直接用webView
                    AgreementActivity.startAct(getActivity(),"https://api.banmi.com/app2017/agreement.html","");
                }
            };
            spannableStringBuilder.setSpan(clickableSpan, 11, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            //下划线
            UnderlineSpan underlineSpan = new UnderlineSpan();
            spannableStringBuilder.setSpan(underlineSpan, 11, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            //前景色
            ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.c_fa6a13));
            spannableStringBuilder.setSpan(foregroundColorSpan, 11, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    
            //需要设置这个ClickableSpan才会有效果
            userAgreement.setMovementMethod(LinkMovementMethod.getInstance());
            userAgreement.setText(spannableStringBuilder);
        }
    
        @OnClick({R.id.phone_number, R.id.send_code, R.id.wechat, R.id.qq, R.id.sina})
        public void onViewClicked(View view) {
            switch (view.getId()) {
                case R.id.send_code:
                    // 获取验证码
                    getVerifyCode();
                    // 跳到输入验证码界面
                    submit();
                    break;
                case R.id.wechat:
                    basePresenter.login(SHARE_MEDIA.WEIXIN);
                    showLoading();
                    break;
                case R.id.qq:
                    basePresenter.login(SHARE_MEDIA.QQ);
                    break;
                case R.id.sina:
                    basePresenter.login(SHARE_MEDIA.SINA);
                    break;
            }
        }
    
        private void getVerifyCode() {
            // 如果倒计时还在进行中就不要请求验证码了
            // 如果不是,让存放验证码的mCode="",然后请求验证码
            // 成功后,把验证码带到输入验证码界面(下面的submit()方法)
            if (time>0&&time<TIME){
                return;
            }
            mCode="";
            basePresenter.getVerifyCode();
        }
    
        private void submit() {
            String phonenumber = phoneNumber.getText().toString().trim();
            if (TextUtils.isEmpty(phonenumber)) {
                ToastUtil.showShort("不能为空");
                return;
            }
            if (phonenumber.matches("[1][34578][0-9]{9}")) {
    
                FragmentManager manager = getActivity().getSupportFragmentManager();
                FragmentTransaction transaction = manager.beginTransaction();
                // 把验证码带到输入验证码界面
                verifyCodeNewFragment = VerifyCodeNewFragment.newInstance(mCode);
                // 如果倒计时还没结束,就不重新倒计时,否则,重新开始倒计时
                if (time>0&&time<TIME){
    
                }else {
                    // 开始倒计时
                    startCountDown();
                }
                transaction.add(R.id.frame, verifyCodeNewFragment);
                transaction.addToBackStack(null);
                transaction.commit();
                // 关闭软键盘 (Tools工具类在最后有)
                Tools.closeKeyBoard(getActivity());
            } else {
                ToastUtil.showShort("手机号不正确");
            }
        }
    
        public void startCountDown() {
            if (mHandler==null){
                mHandler = new Handler();
            }
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    // 当倒计时成负数时停止倒计时并把时间初始化
                    if (time<=0){
                        time=TIME;
                        return;
                    }
                    time--;
                    if (verifyCodeNewFragment!=null){
                        // 把倒计时带到输入验证码界面并显示
                        verifyCodeNewFragment.setCountDownTime(time);
                    }
                    // 通过递归重复运行
                    startCountDown();
                }
            },1000);
        }
    
    
        @Override
        public Activity getAct() {
            return getActivity();
        }
    
        @Override
        public void toastShort(String string) {
    
        }
    
        // 登录成功回到主页面(这个方法在v层的接口中,
        // p层进行三方登录,登录成功之后通过v层接口通知view进入主页面)
        @Override
        public void goToMainActivity() {
            MainActivity.starAct(getActivity());
            getActivity().finish();
            hideLoading();
        }
    
        // 请求验证码接口返回的数据
        // 请求成功
        @Override
        public void onSuccess(VerifyCodeBean verifyCodeBean) {
            String code = verifyCodeBean.getData();
            mCode=code;
            if (verifyCodeNewFragment!=null){
                verifyCodeNewFragment.setData(code);
            }
        }
        
        // 请求失败
        @Override
        public void onFail(String string) {
    
        }
    }
    

    VerifyCodeNewFragment(输入验证码界面)

    public class VerifyCodeNewFragment extends BaseFragment<VerifyCodeNewView, VerifyCodeNewPresenter> implements VerifyCodeNewView {
    
        private static final String TAG = "VerifyCodeNewFragment";
    
        @BindView(R.id.back)
        ImageButton back;
        @BindView(R.id.count_down)
        TextView countDown;
        // IdentifyingCodeView 自定义view 下面有
        @BindView(R.id.icv)
        IdentifyingCodeView icv;
        @BindView(R.id.tv)
        TextView tv;
        int mTime;
        private String mCode;
    
        public VerifyCodeNewFragment() {
            // Required empty public constructor
        }
    
        public static VerifyCodeNewFragment newInstance(String code){
            VerifyCodeNewFragment verifyCodeNewFragment = new VerifyCodeNewFragment();
            Bundle bundle = new Bundle();
            bundle.putString(Constants.VERIFY_CODE,code);
            verifyCodeNewFragment.setArguments(bundle);
            return verifyCodeNewFragment;
        }
    
        @Override
        protected VerifyCodeNewPresenter initPresenter() {
            return new VerifyCodeNewPresenter();
        }
    
        @Override
        protected int initLayoutId() {
            return R.layout.fragment_verify_code_new;
        }
    
        @Override
        protected void initView() {
            super.initView();
    
            Bundle bundle = getArguments();
            mCode = bundle.getString(Constants.VERIFY_CODE, "");
            setData(mCode);
        }
    
        @Override
        protected void initData() {
            super.initData();
    //        basePresenter.getVerifyCode();
        }
    
        @Override
        protected void initListener() {
            super.initListener();
            icv.setOnEditorActionListener(new IdentifyingCodeView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    return false;
                }
    
                @Override
                public void onTextChanged(String s) {
                    // 每输入一个验证码就运行一次
                    autoLogin();
                }
            });
        }
    
        private void autoLogin() {
             // 如果输入的验证码的长度>=4
            if (icv.getTextContent().length()>=4){
                String textContent = icv.getTextContent();
                // 如果输入的验证码和请求回来的验证码一样就登陆
                if (textContent.equals(mCode)){
                    // Toast 自动登录
                    ToastUtil.showShort(getResources().getString(R.string.login_success));
                    // 让输入框的背景变成灰色
                    icv.setBackgroundEnter(false);
                    tv.setText(BaseApp.getRes().getString(R.string.wait_please));
                    // 跳到MainActivity并finish当前的Activity
                    MainActivity.starAct(getActivity());
                    getActivity().finish();
                    // 保存用户的头像、用户名、个性签名等信息
                    SpUtil.setParam(Constants.AVATAR_HD,"");
                    SpUtil.setParam(Constants.USER_NAME,getString(R.string.tourist));
                    SpUtil.setParam(Constants.PERSONALIZED_SIGNATURE,"");
                    SpUtil.setParam(Constants.GENDER,"");
                }else {
                    ToastUtil.showShort(getString(R.string.code_error));
                }
    
            }
        }
    
        @OnClick({R.id.back, R.id.count_down})
        public void onViewClicked(View view) {
            switch (view.getId()) {
                case R.id.back:
                    pop();
                    break;
                case R.id.count_down:
                    // 重新发送按钮
                    // 如果倒计时结束就重新请求验证码
                    // 并让LoginFragment 开始倒计时
                    if (mTime<=0){
                        // 设置按钮可点击
                        countDown.setClickable(false);
                        // 请求验证码
                        basePresenter.getVerifyCode();
                        // 开始倒计时
                        LoginFragment fragment = (LoginFragment) getActivity().getSupportFragmentManager().findFragmentByTag(LoginActivity.TAG);
                        fragment.startCountDown();
                    }
                    break;
            }
        }
    
        /**
         * 碎片手动弹栈
         */
        private void pop() {
            FragmentManager manager = getActivity().getSupportFragmentManager();
            //获取回退栈中碎片的数量
            /*int backStackEntryCount = manager.getBackStackEntryCount();
            Logger.println("回退栈Fragmnet数量:"+backStackEntryCount);*/
            //弹栈
            manager.popBackStack();
        }
    
        @Override
        public void onSuccess(VerifyCodeBean verifyCodeBean) {
            String data = verifyCodeBean.getData();
            tv.setText(BaseApp.getRes().getString(R.string.verify_code)+data);
        }
    
        @Override
        public void onFail(String string) {
    
        }
    
        @Override
        public void onDestroyView() {
            super.onDestroyView();
        }
    
        @Override
        public void showLoading() {
    
        }
    
        @Override
        public void hideLoading() {
    
        }
    
    
        public void setCountDownTime(int time) {
            mTime=time;
            if (countDown!=null){
                if (time<=0){
                    countDown.setClickable(true);
                    countDown.setTextColor(BaseApp.getRes().getColor(R.color.c_fa6a13));
                    countDown.setText(getResources().getString(R.string.count_down));
                }else {
                    countDown.setText(getResources().getString(R.string.count_down)+"("+time+"s)");
                    countDown.setTextColor(BaseApp.getRes().getColor(R.color.c_999999));
                }
            }
        }
    
        public void setData(String code) {
            if (!TextUtils.isEmpty(code)&&tv!=null){
                tv.setText(getResources().getString(R.string.verify_code)+code);
            }
        }
    }
    
    xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        tools:context=".ui.login.fragment.VerifyCodeNewFragment"
        android:orientation="vertical"
        android:background="@color/c_ffffff">
    
        <!-- TODO: Update blank fragment layout -->
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="@dimen/dp_25"
            android:layout_marginLeft="@dimen/dp_20"
            android:layout_marginTop="@dimen/dp_35"
            android:layout_marginRight="@dimen/dp_15">
    
            <ImageButton
                android:id="@+id/back"
                android:layout_width="@dimen/dp_25"
                android:layout_height="@dimen/dp_25"
                android:src="@mipmap/back_white"/>
    
            <TextView
                android:id="@+id/count_down"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/count_down"
                android:textSize="@dimen/sp_14"
                android:textColor="@color/c_999999"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"/>
    
        </RelativeLayout>
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="请输入验证码"
            android:layout_marginTop="@dimen/dp_44"
            android:layout_marginLeft="@dimen/dp_23"
            android:textSize="@dimen/sp_27"
            android:textColor="@color/c_373737"/>
    
        <com.example.lenovo.everywheretravel.widget.IdentifyingCodeView
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/dp_83"
            android:id="@+id/icv"
            app:icv_et_number="4"
            app:icv_et_width="@dimen/dp_44"
            app:icv_et_divider_drawable="@drawable/divider_icv"
            app:icv_et_text_color="@color/c_636363"
            app:icv_et_text_size="@dimen/sp_20"
            app:icv_et_height="@dimen/dp_56"
            android:layout_width="wrap_content"
            android:layout_height="@dimen/dp_56">
    
        </com.example.lenovo.everywheretravel.widget.IdentifyingCodeView>
    
        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="@dimen/sp_14"
            android:textColor="@color/c_fa6a13"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/dp_33"/>
    
    </LinearLayout>
    

    IdentifyingCodeView (自定义验证码输入框)

    /**
     * 类名:      IdentifyingCodeView
     * 创建时间:  2017/10/30 20:07
     * 描述:     自定义验证码输入框
     *
     */
    
    public class IdentifyingCodeView extends RelativeLayout {
    
        private LinearLayout containerEt;
    
        private EditText et;
        // 输入框数量
        private int mEtNumber;
        // 输入框的宽度
        private int mEtWidth;
        private int mEtHeight;
        //输入框分割线
        private Drawable mEtDividerDrawable;
        //输入框文字颜色
        private int mEtTextColor;
        //输入框文字大小
        private float mEtTextSize;
        //输入框获取焦点时背景
        private int mEtBackgroundDrawableFocus;
        //输入完成点击完成时的背景
        private int mEtBackgroundEnter = R.drawable.shape_icv_et_bg_enter;
    
        // 输入框没有焦点时背景
        private int mEtBackgroundDrawableNormal;
        //存储TextView的数据 数量由自定义控件的属性传入
        private TextView[] mTextViews;
        /**
         * edittext 软键盘监听
         * actionDone 完成 对应 EditorInfo.IME_ACTION_DONE
         actionGo 前进 对应 EditorInfo.IME_ACTION_GO
         actionNext 下一项 对应 EditorInfo.IME_ACTION_NEXT
         actionNone 无动作 对应 EditorInfo.IME_ACTION_NONE
         actionPrevious 上一项 对应 EditorInfo.IME_ACTION_PREVIOUS
         actionSearch 搜索 对应 EditorInfo.IME_ACTION_SEARCH
         actionUnspecified 未指定 对应 EditorInfo.IME_ACTION_UNSPECIFIED
         actionSend 发送 对应 EditorInfo.IME_ACTION_SEND
         */
        private OnEditorActionListener actionListener;
    
    
        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);
            et.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    return actionListener.onEditorAction(v,actionId,event);
                }
            });
    
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IdentifyingCodeView, defStyleAttr, 0);
            mEtNumber = typedArray.getInteger(R.styleable.IdentifyingCodeView_icv_et_number, 1);
            mEtWidth = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_width, 42);
            mEtHeight = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_height, 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.getResourceId(R.styleable.IdentifyingCodeView_icv_et_bg_focus, R.drawable.shape_icv_et_bg_normal);
            mEtBackgroundDrawableNormal = typedArray.getResourceId(R.styleable.IdentifyingCodeView_icv_et_bg_normal, R.drawable.shape_icv_et_bg_normal);
            //释放资源
            typedArray.recycle();
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            initTextViews(getContext(), mEtNumber, mEtWidth,mEtHeight, mEtDividerDrawable, mEtTextSize, mEtTextColor);
            initEtContainer(mTextViews);
            setListener();
        }
    
    
        //初始化TextView
        private void initTextViews(Context context, int etNumber, int etWidth, int etHeight, 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 TextView(context);
                textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,etTextSize);
                textView.setTextColor(etTextColor);
                textView.setWidth(etWidth);
                textView.setHeight(etHeight);
                if (i == 0) {
                    textView.setBackgroundResource(mEtBackgroundDrawableFocus);
                } else {
                    textView.setBackgroundResource(mEtBackgroundDrawableNormal);
                }
                textView.setGravity(Gravity.CENTER);
    
                textView.setFocusable(false);
    
                mTextViews[i] = textView;
            }
        }
    
        //初始化存储TextView 的容器
        private void initEtContainer(TextView[] mTextViews) {
            for (int i = 0; i < mTextViews.length; i++) {
                containerEt.addView(mTextViews[i]);
            }
        }
    
    
        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) {
                    if (actionListener != null){
                        actionListener.onTextChanged(charSequence.toString());
                    }
                }
    
                @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.setBackgroundResource(mEtBackgroundDrawableNormal);
                    if (i < mEtNumber - 1) {
                        mTextViews[i + 1].setBackgroundResource(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.setBackgroundResource(mEtBackgroundDrawableFocus);
                    if (i < mEtNumber - 1) {
                        mTextViews[i + 1].setBackgroundResource(mEtBackgroundDrawableNormal);
                    }
                    break;
                }
            }
        }
    
    
        /**
         * 获取输入文本
         *
         * @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].setBackgroundResource(mEtBackgroundDrawableFocus);
                } else {
                    mTextViews[i].setBackgroundResource(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();
        }
        public interface OnEditorActionListener {
            boolean onEditorAction(TextView v, int actionId, KeyEvent event);
    
            void onTextChanged(String s);
        }
    
        public void setOnEditorActionListener(OnEditorActionListener actionListener) {
            this.actionListener = actionListener;
        }
    
        public void setEditable(boolean b){
            this.et.setFocusable(b);
            this.et.setFocusableInTouchMode(b);
        }
    
        /**
         * 设置不允许输入后的背景
         * @param b true,白色背景,false灰色背景
         */
        public void setBackgroundEnter(boolean b){
            if (!b){
                for (int i = 0; i < mTextViews.length; i++) {
                    mTextViews[i].setBackgroundResource(mEtBackgroundEnter);
                }
            }else {
                boolean flag = false;
                for (int i = 0; i < mTextViews.length; i++) {
                    String trim = mTextViews[i].getText().toString().trim();
                    if (!TextUtils.isEmpty(trim)){
                        mTextViews[i].setBackgroundResource(mEtBackgroundDrawableNormal);
                    }else {
                        if (!flag){
                            mTextViews[i].setBackgroundResource(mEtBackgroundDrawableFocus);
                            flag = true;
                        }else {
                            mTextViews[i].setBackgroundResource(mEtBackgroundDrawableNormal);
                        }
                    }
                }
            }
        }
    }
    
    layout_identifying_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="match_parent">
    
        <LinearLayout
            android:id="@+id/container_et"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:showDividers="middle">
    
        </LinearLayout>
    
        <com.example.lenovo.everywheretravel.widget.OwnEditText
            android:id="@+id/et"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/transparent"
            android:imeOptions="actionDone"
            android:singleLine="true"
            android:inputType="number"/>
    
    </RelativeLayout>
    
    OwnEditText
    public class OwnEditText extends AppCompatEditText {
    
        private OnKeyListener keyListener;
    
        public OwnEditText(Context context) {
            super(context);
        }
    
        public OwnEditText(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public OwnEditText(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
            return new MyInputConnection(super.onCreateInputConnection(outAttrs), true);
        }
    
        private class MyInputConnection extends InputConnectionWrapper {
    
            /**
             * Initializes a wrapper.
             * <p>
             * <p><b>Caveat:</b> Although the system can accept {@code (InputConnection) null} in some
             * places, you cannot emulate such a behavior by non-null {@link InputConnectionWrapper} that
             * has {@code null} in {@code target}.</p>
             *
             * @param target  the {@link InputConnection} to be proxied.
             * @param mutable set {@code true} to protect this object from being reconfigured to target
             *                another {@link InputConnection}.  Note that this is ignored while the target is {@code null}.
             */
            public MyInputConnection(InputConnection target, boolean mutable) {
                super(target, mutable);
            }
    
            @Override
            public boolean sendKeyEvent(KeyEvent event) {
                if (keyListener != null) {
                    keyListener.onKey(OwnEditText.this, event.getKeyCode(), event);
                }
                return super.sendKeyEvent(event);
            }
    
            @Override
            public boolean deleteSurroundingText(int beforeLength, int afterLength) {
                // 在删除时,输入框无内容,或者删除以后输入框无内容
                if (beforeLength == 1 || afterLength == 0 || beforeLength == 0) {
                    return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                            && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
                }
                return super.deleteSurroundingText(beforeLength, afterLength);
            }
        }
    
        // 设置监听回调
        public void setSoftKeyListener(OnKeyListener listener) {
            keyListener = listener;
        }
    }
    
    R.drawable.shape_icv_et_bg_normal
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <!-- 大小 -->
    
        <corners android:radius="2dp" />
    
        <stroke
            android:width="1dp"
            android:color="#C7C7CD" />
    </shape>
    
    R.drawable.shape_icv_et_bg_enter
    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <corners android:radius="2dp" />
    
        <stroke
            android:width="1dp"
            android:color="#aaa" />
        <solid android:color="@color/c_eaeaea"/>
    </shape>
    
    在values文件夹下创建attrs.xml
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!-- 自定义验证码输入框-->
        <declare-styleable name="IdentifyingCodeView">
            <!--输入框的数量-->
            <attr name="icv_et_number" format="integer" />
            <!--输入框的宽度-->
            <attr name="icv_et_width" format="dimension|reference" />
            <!--输入框的高度-->
            <attr name="icv_et_height" 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>
    </resources>
    

    LoginFragment中关闭软件盘用到的工具类Tools

    Tools

    https://www.jianshu.com/p/c0d7d31d55e2
    https://www.jianshu.com/p/ea54614dd660

    UIUtils

    https://www.jianshu.com/p/1f170351a7c7

    再来一个验证码输入框的自定义 View

    AuthCodeView
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.graphics.RectF;
    import android.support.v7.widget.AppCompatEditText;
    import android.text.Editable;
    import android.text.InputFilter;
    import android.text.TextPaint;
    import android.text.TextWatcher;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    import android.view.KeyEvent;
    import android.view.View;
    import android.view.inputmethod.EditorInfo;
    
    public class AuthCodeView extends AppCompatEditText implements View.OnKeyListener, TextWatcher {
        private String TAG = "AuthCodeView";
        private Paint mPaint;
        // 一个密码所占的宽度
        private int mPasswordItemWidth;
        // 密码的个数默认为 4 位数
        private int mPasswordNumber = 4;
        // 背景边框颜色
        private int mBgColor = Color.parseColor("#ffffff");
        // 背景边框大小
        private int mBgSize = 3;
        // 背景边框圆角大小
        private int mBgCorner = 0;
        // 密码圆点的颜色
        private int mPasswordColor = mBgColor;
        // 密码圆点的半径大小
        private int mPasswordRadius = 4;
        // Item 之间的间隔
        private int itemDistance = 0;
        private int paddingTop = dip2px(16) * -1;
        // 是否为密码模式,如果不是密码模式,就明文展示
        private boolean isPassWord = false;
        private int iTextSize = 14;
        // 判断是否已经回调给前面了
        private volatile boolean isHaveNotice = false;
        // 输入框有内容时的背景
        private Bitmap inputBitmap =
                BitmapFactory.decodeResource(this.getResources(), R.drawable.bg_num_input);
        // 输入框没有内容时的背景
        private Bitmap normalBitmap =
                BitmapFactory.decodeResource(this.getResources(), R.drawable.bg_num_normal);
    
        public AuthCodeView(Context context) {
            this(context, null);
        }
    
        public AuthCodeView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public AuthCodeView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            // 初始化一些布局必须参数
            initAttributeSet(context, attrs);
            // 初始化画笔 + 个数计算
            init();
            // 设置输入模式是密码
            if (isPassWord) {
                setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
            } else {
                setInputType(EditorInfo.TYPE_CLASS_NUMBER);
            }
            // 不显示光标
            setCursorVisible(false);
            // 获取焦点
            this.setFocusable(true);
            this.setFocusableInTouchMode(true);
            this.requestFocus();
            setOnKeyListener(this);
        }
    
        private OnInputPasswordEditListener onInputPasswordEditListener;
    
        @Override
        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
            if (keyCode == KeyEvent.KEYCODE_DEL) {
                isHaveNotice = false;
            }
            return false;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPasswordItemWidth = getMeasuredHeight() - paddingTop;
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            // 平均下来的距离
            itemDistance =
                    (getWidth() - (mPasswordNumber * mPasswordItemWidth)) / (mPasswordNumber + 1);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            // 绘制背景
            drawBg(itemDistance, canvas);
            drawHidePassword(canvas);
        }
    
        public interface OnInputPasswordEditListener {
            void onFinishInput();
        }
    
        public void setOnInputPasswordEditListener(OnInputPasswordEditListener
                                                           onInputPasswordEditListener) {
            this.onInputPasswordEditListener = onInputPasswordEditListener;
        }
    
        private void initAttributeSet(Context context, AttributeSet attrs) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AuthCodeView);
            mBgSize = typedArray.getInteger(R.styleable.AuthCodeView_bgSize, 1);
            mPasswordNumber = typedArray.getInteger(R.styleable.AuthCodeView_passwordNumber, 6);
            mBgCorner = typedArray.getInteger(R.styleable.AuthCodeView_bgCorner, 5);
            mPasswordRadius = typedArray.getInteger(R.styleable.AuthCodeView_passwordRadius, 4);
            mPasswordColor = typedArray.getColor(R.styleable.AuthCodeView_passwordColor, mBgColor);
    //        mBgColor = typedArray.getColor(R.styleable.AuthCodeView_bgColor, mBgColor);
            isPassWord = typedArray.getBoolean(R.styleable.AuthCodeView_isPassword, true);
            iTextSize = typedArray.getInteger(R.styleable.AuthCodeView_iTextSise, 16);
            typedArray.recycle();
            mBgSize = dip2px(mBgSize);
            mBgCorner = dip2px(mBgCorner);
            mPasswordRadius = dip2px(mPasswordRadius);
            iTextSize = sp2px(context, iTextSize);
            this.setFilters(new InputFilter[]{new InputFilter.LengthFilter(mPasswordNumber)});
        }
    
        private void init() {
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setDither(true);
            mPaint.setColor(mBgColor);
            // 设置画笔的字体
            mPaint.setTypeface(Typeface.createFromAsset(this.getResources().getAssets(),
                          "fonts/gilroy_medium.otf"));
        }
    
        private void drawBg(int startX, Canvas canvas) {
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.BLACK);
            mPaint.setStrokeWidth(3);
            int passwordLength = getText().toString().trim().length();
            for (int i = 0; i < mPasswordNumber; i++) {
                RectF rectF = new RectF(startX, paddingTop / 2,
                        startX + mPasswordItemWidth, mPasswordItemWidth + paddingTop / 2);
    //            canvas.drawRoundRect(rectF, mBgCorner, mBgCorner, mPaint);
    //            canvas.drawLine(rectF.left, rectF.bottom, rectF.right, rectF.bottom, mPaint);
                if (i < passwordLength) {
                    canvas.drawBitmap(inputBitmap,
                            new Rect(0, 0, inputBitmap.getWidth(), inputBitmap.getHeight()),
                            rectF, null);
                } else {
                    canvas.drawBitmap(normalBitmap,
                            new Rect(0, 0, normalBitmap.getWidth(), normalBitmap.getHeight()),
                            rectF, null);
                }
                startX = startX + mPasswordItemWidth + itemDistance;
            }
        }
    
        private void drawHidePassword(Canvas canvas) {
            int passwordLength = getText().toString().trim().length();
            mPaint.setColor(mPasswordColor);
            mPaint.setStyle(Paint.Style.FILL);
            if (!isPassWord) {
                // 不是密码模式
                mPaint.setTextSize(iTextSize);
                String text = getText().toString();
                // 1  2  3
                for (int i = 0; i < passwordLength; i++) {
                    int cx = itemDistance + i * mPasswordItemWidth + mPasswordItemWidth / 2
                            + i * itemDistance;
                    String aaa = text.substring(i, i + 1);
                    // 文字处理
                    TextPaint paint = this.getPaint();
                    // 计算baseLine
                    int center = getMeasuredHeight() / 2 + Math.abs(paddingTop / 4);
                    int baseLine = (int) (center + (paint.getFontMetrics().bottom -
                            paint.getFontMetrics().top) / 2 - paint
                            .getFontMetrics().bottom);
                    int fontWidth = (int) mPaint.measureText(aaa);
                    canvas.drawText(aaa, cx - fontWidth / 2, baseLine, mPaint);
                }
            } else {
                // 密码处理
                for (int i = 0; i < passwordLength; i++) {
                    int cx = itemDistance + i * mPasswordItemWidth + mPasswordItemWidth / 2
                            + i * itemDistance;
                    canvas.drawCircle(cx, getHeight() / 2, mPasswordRadius, mPaint);
                }
            }
    
            if (passwordLength == mPasswordNumber && !isHaveNotice) {
                if (onInputPasswordEditListener != null) {
                    onInputPasswordEditListener.onFinishInput();
                    isHaveNotice = true;
                }
            }
        }
    
        public void cleanData() {
            this.setText("");
            isHaveNotice = false;
        }
    
        private int dip2px(int dip) {
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    dip, getResources().getDisplayMetrics());
        }
    
        public int px2sp(Context context, float pxValue) {
            final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
            return (int) (pxValue / fontScale + 0.5f);
        }
    
        public int sp2px(Context context, float spValue) {
            final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
            return (int) (spValue * fontScale + 0.5f);
        }
    
        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            int len = charSequence.length();
            // 通过监听文本发生变化来判断当前文本的长度,用来弥补系统自带输入法的软键盘delete键无法监听到的问题。
            if (len == mPasswordNumber) {
                isHaveNotice = false;
            }
        }
    
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }
    
        @Override
        public void afterTextChanged(Editable editable) {
        }
    
        public void releaseBitmap() {
            try {
                if (inputBitmap != null && !inputBitmap.isRecycled()) {
                    inputBitmap.recycle();
                }
                if (normalBitmap != null && !normalBitmap.isRecycled()) {
                    normalBitmap.recycle();
                }
            } catch (Throwable t) {
                t.printStackTrace();
                HLog.d(TAG + " releaseBitmap exception:" + t.getMessage());
            }
        }
    }
    

    使用:

    <com.example.demo.widget.AuthCodeView
                android:id="@+id/safe_check_authcode"
                android:layout_width="@dimen/dp230dp"
                android:layout_height="@dimen/dp65dp"
                android:layout_centerHorizontal="true"
                android:focusable="true"
                android:inputType="number"
                app:bgColor="#ffffff"
                app:iTextSise="23"
                app:isPassword="false"
                app:passwordColor="@color/white"
                app:passwordNumber="4" />
    

    Activity

    // 获取到键盘的输入后
    authCodeView.setText(phone.toString());
    // 在 destroy 时需要注意,要调用 releaseBitmap 方法
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (authCodeView != null) {
            authCodeView.releaseBitmap();
        }
    }
    

    相关文章

      网友评论

          本文标题:倒计时发送验证码+验证码输入框

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