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

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

作者: 沈溺_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();
    }
}

相关文章

  • iOS验证码倒计时实现,退出进入以后继续倒计时

    需求 App中有很多页面地方要发送验证码,涉及到验证码的地方肯定会有倒计时功能。产品要求发送验证码以后,在倒计时结...

  • iOS-UIButton倒计时

    一般倒计时的使用场景就两种:发送短信验证码倒计时广告页倒计时 一、发送短信验证码倒计时 这种情况下,正在倒计时的按...

  • 链式调用

    这个是, 短信验证码的发送接口, 然后,验证码发送成功调用倒计时函数,不成功不调用倒计时,我一开始的想法,是返回一...

  • Axure RP9 倒计时效果

    在注册时常用到发送验证码的功能,输入手机号后点击“发送验证码” 此时按钮置灰 并开始倒计时,倒计时结束后,可以再次...

  • 登录注册---验证码倒计时的实现

    先上图: OK!今天要实现的就是发送验证码的倒计时。 首先搭建好你需要的页面。给“发送验证码”sendBtn添加一...

  • Android 获取验证码倒计时实现

    1. 验证码输入框和获取验证码按钮布局 xml代码: 效果如下: 2. 根据id设置Button点击事件触发倒计时...

  • 输入正确的手机号码才可以发送验证码 2018-08-10

    实现输入正确的手机号码才可以发送验证码的功能,check函数在mounted中调用 发送验证码的倒计时效果

  • 短信验证码深度解剖

    一、短信验证码运作机制 1. 验证码加密发送 在APP中点击发送验证码,向后台发送一个发送验证码请求; 后台收到请...

  • 获取验证码按钮的状态

    有四个状态 获取验证码 发送中 倒计时 60-1 重新获取 1.默认为获取验证码首先 点击发送请求 中 按钮为 发...

  • Android 用 RxBinding 与 RxJava2 实现

    场景:注册账号页面时,我们点击按钮发送验证码,在等待验证码时,界面会有倒计时提示,这此期间按钮不可点击。当倒计时结...

网友评论

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

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