类似上图,几个输入框横线排列。
具体思路:使用linearLayout 横向添加几个ediTtext,第一个首先获取焦点,当输入完成第一个,焦点依次传递到下一个,如果是键盘删除按钮删除,判断当前获取焦点的输入框是否有内容,如果有内容则清空内容,如果无内容那么焦点往前一个输入框传递。
关于动态修改光标的样式,EditText没有提供api,那么一想肯定就是反射了,代码如下:
/** * 改变光标的颜色 * @param editText */ public void changeCursorDrawable(EditText editText) { try { Field f = TextView.class.getDeclaredField("mCursorDrawableRes"); f.setAccessible(true); f.set(editText, R.drawable.cursor_drawable); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
控件完整代码如下所示:
package com.job.android.views; import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.Nullable; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import com.job.android.R; import java.lang.reflect.Field; /** * Created by sima.tu on 2018/8/31. * 验证码的输入框 */ public class VerificationCodeView extends LinearLayout implements TextWatcher, View.OnFocusChangeListener, View.OnKeyListener { private static String TAG = VerificationCodeView.class.getName(); private Context mContext; private int codeViewNumber; // 子输入框的个数 private int mTextSize; //输入框字体大小 private int mTextColor; private int codeViewChildMarginRightAndLeft; // 子输入框的左右外间距 private EditText mNexInputEditText; //下一个即将获取焦点的输入框 private EditText mCurrentFocusedEditText; //当前占有焦点的输入框 private InputCompleteListener inputCompleteListener; private int mFocusedIndex = -1; //当前占有焦点的输入框的下标 /** * @return */ public EditText getmCurrentFocusedEditText() { return mCurrentFocusedEditText; } /** * 设置当前获取焦点的editText 方便外界设置 * * @param mCurrentFocusedEditText */ public void setmCurrentFocusedEditText(EditText mCurrentFocusedEditText) { this.mCurrentFocusedEditText = mCurrentFocusedEditText; } public void setInputCompleteListener(InputCompleteListener inputCompleteListener) { this.inputCompleteListener = inputCompleteListener; } public VerificationCodeView(Context context) { super(context); } public VerificationCodeView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); this.mContext = context; initView(context, attrs); } public VerificationCodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; initView(context, attrs); } public void initView(Context context, AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.VerificationCodeView); if (null == array) { return; } int count = array.getIndexCount(); for (int i = 0; i < count; i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.VerificationCodeView_codeViewNumber: codeViewNumber = array.getInt(attr, 6); break; case R.styleable.VerificationCodeView_codeViewChildMarginRightAndLeft: codeViewChildMarginRightAndLeft = (int) array.getDimension(attr,0); break; } } array.recycle(); for (int i = 0; i < codeViewNumber; i++) { EditText editText = new EditText(context); LayoutParams params = new LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT); params.setMargins(10, 0, 10, 0); params.weight = 1f; params.gravity = Gravity.CENTER; editText.setLayoutParams(params); editText.setGravity(Gravity.CENTER); // 设置让光标居中 editText.setBackground(getResources().getDrawable(R.drawable.edittext_underline)); editText.setBackgroundResource(R.drawable.edittext_underline); // 设置光标的下划线样式 editText.setInputType(InputType.TYPE_CLASS_NUMBER); editText.setTextSize(30); editText.setTextColor(getResources().getColor(R.color.gray_444444)); editText.addTextChangedListener(this); editText.setOnFocusChangeListener(this); editText.setOnKeyListener(this); if (i == 0) { mNexInputEditText = editText; } editText.setTag(i); addView(editText); //由于无法直接在代码中改变光标样式 这里使用反射去修改光标样式 changeCursorDrawable(editText); if (i == 0) { //设置第一个editText获取焦点 editText.requestFocus(); } } } /** * 改变光标的颜色 * @param editText */ public void changeCursorDrawable(EditText editText) { try { Field f = TextView.class.getDeclaredField("mCursorDrawableRes"); f.setAccessible(true); f.set(editText, R.drawable.cursor_drawable); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (s.length() < 1) { return; } if (s.toString().length() > 1) { String string = s.toString().substring(0, 1); ((EditText) getChildAt(mFocusedIndex)).setText(string); } } @Override public void afterTextChanged(Editable s) { if (mFocusedIndex == codeViewNumber - 1 && s.toString().length() > 0) { ((EditText) getChildAt(mFocusedIndex)).setCursorVisible(false); } if (s.toString().length() < 1) { return; } if (s.toString().length() > 0 && s.toString().length() <= 1) { //如果不是最后一个输入框 if (mFocusedIndex != codeViewNumber - 1) { mNexInputEditText = ((EditText) getChildAt(mFocusedIndex + 1)); mNexInputEditText.requestFocus(); // 焦点移动至下一个EditText } else { //因为最后一个输入框一直获取着焦点,如果键盘未消失一直改变最后一位的数字,会一直回调输入完成的监听,这里让键盘消失 hideInputMethod(mContext, mNexInputEditText); StringBuilder builder = new StringBuilder(); for (int i = 0; i < codeViewNumber; i++) { EditText editText = (EditText) getChildAt(i); builder.append(editText.getText().toString()); } inputCompleteListener.inputSuccess(builder.toString()); return; } } } @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { mFocusedIndex = (int) v.getTag(); mCurrentFocusedEditText = (EditText) v; setmCurrentFocusedEditText(mCurrentFocusedEditText); } else { } } /** * 给EditText 设置键盘的删除键监听 * * @param v * @param keyCode * @param event * @return */ @Override public boolean onKey(View v, int keyCode, KeyEvent event) { block300ms(v); if (keyCode == KeyEvent.KEYCODE_DEL) { if (((EditText) v).getText().length() > 0 && mFocusedIndex == codeViewNumber - 1) { ((EditText) v).setText(""); ((EditText) v).setCursorVisible(true); mNexInputEditText = (EditText) v; } else if (((EditText) v).getText().length() == 0) { if (mFocusedIndex > 0) { mNexInputEditText = ((EditText) getChildAt(mFocusedIndex - 1)); mNexInputEditText.setText(""); mNexInputEditText.requestFocus(); } else { mNexInputEditText = ((EditText) getChildAt(0)); } } } if (keyCode == KeyEvent.KEYCODE_ENTER) { reSetInputState(); return true; } return false; } /** * 传递焦点 */ public void transmitFocus(int keyCode){ mCurrentFocusedEditText.clearFocus(); mNexInputEditText.requestFocus(); } public void block300ms(final View paramView) { if (null == paramView) { return; } paramView.setClickable(false); paramView.postDelayed(new Runnable() { @Override public void run() { paramView.setClickable(true); } }, 300); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (MotionEvent.ACTION_DOWN == ev.getAction()) { mNexInputEditText.requestFocus(); showInputMethod(mContext, mNexInputEditText); return true; } return false; } /** * 重置输入框的状态 */ public void reSetInputState() { for (int i = getChildCount() - 1; i >= 0; i--) { EditText editText = (EditText) getChildAt(i); editText.setText(""); if (i == 0) { mNexInputEditText = editText; editText.requestFocus(); showInputMethod(mContext, editText); } } } /** * 输入完成的监听 */ public interface InputCompleteListener { void inputSuccess(String content); } /** * 显示输入法 */ public static void showInputMethod(Context context, View view) { try { InputMethodManager imeOptions = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); if (imeOptions.isActive(view)) { imeOptions.showSoftInput(view, InputMethodManager.SHOW_FORCED); } } catch (Throwable e) { } } /** * 隐藏输入法 */ public static void hideInputMethod(Context context, View view) { try { InputMethodManager imeOptions = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); if (imeOptions.isActive(view)) { // 这里用 imeOptions.hideSoftInputFromWindow(view.getWindowToken(), 0); } } catch (Throwable e) { } } }
很简单,控制一些光标的传递逻辑以及与键盘的交互逻辑即可。