公司的项目有个关于验证码的自定义文本框,要求实现锁定一次光标,输入框自动换行,哎呦,对我这个菜鸟来说,真是着急死了,各种扒拉,各种调试,最后还好实现了,但是感觉跟预期的还是差了那么一点点。。。。记录一哈哈吧,感谢各位技术大佬们。。。。
public class CodeInputView extends View {
private Paint mPaint; //绘制对象
private Handler mHandler; //用于指示输入提示
private boolean isDrawText;//是否绘制文本
private boolean isInputState = false;//是否输入状态
private boolean mDrawRemindLineState; //竖线状态控制 true 显示
private int mInputStateBoxColor; //输入状态下框颜色
private int mNoInputStateBoxColor;//未输入状态下框颜色
private int mRemindLineColor; //提示输入线颜色
private int mInputStateTextColor; //输入后文字图案提示颜色
private int mBoxDrawType = 0;//盒子图画类型 0 矩形 2圆形 3横线
private int mShowPassType = 0;// 0 提示图案为实心圆 1提示图案为*
private boolean isShowRemindLine = true;// true 显示提示光标 默认显示
private int mWidth = 40;
private int mheight = 40;
private String mPassText = "";//要绘制的文字
private Context mContext;
private int mDrawTxtSize = 25;
private int mDrawBoxLineSize = 4;
public void setInputStateColor(int inputColor) {
this.mInputStateBoxColor = inputColor;
}
public void setmPassText(String mPassText) {
this.mPassText = mPassText;
}
public void setNoinputColor(int noinputColor) {
this.mNoInputStateBoxColor = noinputColor;
}
public void setInputState(boolean input) {
isInputState = input;
}
public void setRemindLineColor(int line_color) {
this.mRemindLineColor = line_color;
}
public void setInputStateTextColor(int drc_color) {
this.mInputStateTextColor = drc_color;
}
public CodeInputView(Context context) {
this(context, null);
}
public CodeInputView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public void setmBoxDrawType(int mBoxDrawType) {
this.mBoxDrawType = mBoxDrawType;
}
public void setmShowPassType(int mShowPassType) {
this.mShowPassType = mShowPassType;
}
public void setmDrawTxtSize(int mDrawTxtSize) {
this.mDrawTxtSize = mDrawTxtSize;
}
public void setmIsShowRemindLine(boolean mIsShowShan) {
this.isShowRemindLine = mIsShowShan;
}
public void setmDrawBoxLineSize(int mDrawBoxLineSize) {
this.mDrawBoxLineSize = mDrawBoxLineSize;
}
public CodeInputView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
// 初始化画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);
// 设置“空心”的外框的宽度
mPaint.setStrokeWidth(mDrawBoxLineSize);
mPaint.setPathEffect(new CornerPathEffect(1));
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
if (modeWidth == MeasureSpec.EXACTLY) {//如果是精确测量 则直接返回值
width = sizeWidth;
} else {//指定宽度的大小
width = mWidth;
if (modeWidth == MeasureSpec.AT_MOST) {//如果是最大值模式 取当中的小值 防止超出父类控件的最大值
width = Math.min(width, sizeWidth);
}
}
if (modeHeight == MeasureSpec.EXACTLY) {//如果是精确测量 则直接返回值
height = sizeHeight;
} else {//指定高度的大小
height = mheight;
if (modeHeight == MeasureSpec.AT_MOST) {//如果是最大值模式 取当中的小值 防止超出父类控件的最大值
height = Math.min(height, sizeHeight);
}
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
drawInputBox(canvas); //绘制输入框
drawRemindLine(canvas); //绘制提醒线
drawInputTextOrPicture(canvas); //绘制输入文本或密码图案
}
/**
* //绘制输入文本或密码图案
*
* @param canvas
*/
private void drawInputTextOrPicture(Canvas canvas) {
if (isDrawText) { //是否需要进行绘制
mPaint.setColor(ContextCompat.getColor(mContext, mInputStateTextColor));
mPaint.setStyle(Paint.Style.FILL);//实心
switch (mShowPassType) {
case 0: //绘制圆心
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 8, mPaint);
break;
case 1: //绘制*
mPaint.setTextSize(getMeasuredWidth() / 2 + 10);
float stringWidth = mPaint.measureText("*");
float baseY = (getMeasuredHeight() / 2 - ((mPaint.descent() + mPaint.ascent()) / 2)) + stringWidth / 3; //实现y轴居中方法
float baseX = getMeasuredWidth() / 2 - stringWidth / 2; //实现X轴居中方法
canvas.drawText("*", baseX, baseY, mPaint);
break;
case 2: //绘制输入数据
mPaint.setTextSize(mDrawTxtSize);//绘制字体大小
float stringWidth2 = mPaint.measureText(mPassText);
float baseY2 = (getMeasuredHeight() / 2 - ((mPaint.descent() + mPaint.ascent()) / 2)) + stringWidth2 / 5; //实现y轴居中方法
float baseX2 = getMeasuredWidth() / 2 - stringWidth2 / 2; //实现X轴居中方法
canvas.drawText(mPassText, baseX2, baseY2, mPaint); //文字
break;
}
}
}
/**
* 绘制提示线
*
* @param canvas
*/
private void drawRemindLine(Canvas canvas) {
if (mDrawRemindLineState && isShowRemindLine) { // mDrawRemindLineState 控制闪烁情况 //isShowRemindLine 是否绘制提示线
int line_height = getMeasuredWidth() / 2 - 10;
line_height = line_height < 0 ? getMeasuredWidth() / 2 : line_height;
mPaint.setStyle(Paint.Style.FILL);//实心
mPaint.setColor(ContextCompat.getColor(mContext, mRemindLineColor));
canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - line_height / 2, getMeasuredWidth() / 2, getMeasuredHeight() / 2 + line_height / 2, mPaint);
}
}
/**
* 绘制输入框
*
* @param canvas
*/
private void drawInputBox(Canvas canvas) {
if (isInputState) { //是否是输入状态 输入状态和未输入状态颜色区分
mPaint.setColor(ContextCompat.getColor(mContext, mInputStateBoxColor));
} else {
mPaint.setColor(ContextCompat.getColor(mContext, mNoInputStateBoxColor));
}
mPaint.setStyle(Paint.Style.STROKE);//空心
switch (mBoxDrawType) {
case 1: //绘制圆形
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, getMeasuredWidth() / 2 - 5, mPaint);
break;
case 2: //绘制横线
canvas.drawLine(0, getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight(), mPaint);
break;
default:// 绘制矩形
RectF rect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
canvas.drawRoundRect(rect, 6, 6, mPaint);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}
/**
* 刷新状态
*
* @param isinput 是否已经输入过
*/
public void updateInputState(boolean isinput) {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
if (isinput) {
isInputState = true;
isDrawText = true;
} else {
isInputState = false;
isDrawText = false;
}
mDrawRemindLineState = false;
invalidate();
}
/**
* 设置为选中输入状态
*/
public void startInputState() {
isInputState = true;
isDrawText = false;
if (mHandler == null) {
mHandler = new Handler();
}
mHandler.removeCallbacksAndMessages(null);
if (!isShowRemindLine) {
invalidate();
return;
}
mHandler.post(new Runnable() {
@Override
public void run() { //循环绘制 造成闪动状态
mDrawRemindLineState = !mDrawRemindLineState;
invalidate();
mHandler.postDelayed(this, 800);
}
});
}
自定义线性布局:
public class CodeInputLayout extends LinearLayout {
private int maxLength = 6; //长度
private int inputIndex = 0; //设置子LinearLayout的子View状态index
private int lInputIndex = 0;//设置子LinearLayout状态index
private List<String> mPassList;//储存输入
private pwdChangeListener pwdChangeListener;//状态改变监听
private Context mContext;
private boolean mIsShowInputLine;
private int mInputColor;
private int mNoinputColor;
private int mLineColor;
private int mTxtInputColor;
private int mDrawType;
private int mInterval;
private int mItemWidth;
private int mItemHeight;
private int mShowPassType;
private int mTxtSize;
private int mBoxLineSize;
public void setPwdChangeListener(pwdChangeListener pwdChangeListener) {
this.pwdChangeListener = pwdChangeListener;
}
public CodeInputLayout(Context context) {
this(context, null);
}
public CodeInputLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CodeInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs);
}
/**
* 初始化View
*/
private void initView(Context context, AttributeSet attrs) {
mContext = context;
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PassWordLayoutStyle);
mInputColor = ta.getResourceId(R.styleable.PassWordLayoutStyle_box_input_color, R.color.gray_c1c1c1);
mNoinputColor = ta.getResourceId(R.styleable.PassWordLayoutStyle_box_no_input_color, R.color.gray_c1c1c1);
mLineColor = ta.getResourceId(R.styleable.PassWordLayoutStyle_input_line_color, R.color.blue_3385ff);
mTxtInputColor =
ta.getResourceId(R.styleable.PassWordLayoutStyle_text_input_color, R.color.blue_3385ff);
mDrawType = ta.getInt(R.styleable.PassWordLayoutStyle_box_draw_type, 0);
mInterval = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_interval_width, 4);
maxLength = ta.getInt(R.styleable.PassWordLayoutStyle_pass_leng, 6);
mItemWidth = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_item_width, 40);
mItemHeight = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_item_height, 40);
mShowPassType = ta.getInt(R.styleable.PassWordLayoutStyle_pass_inputed_type, 0);
mTxtSize = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_draw_txt_size, 32);
mBoxLineSize = ta.getDimensionPixelOffset(R.styleable.PassWordLayoutStyle_draw_box_line_size, 4);
mIsShowInputLine = ta.getBoolean(R.styleable.PassWordLayoutStyle_is_show_input_line, true);
ta.recycle();
mPassList = new ArrayList<>();
setOrientation(VERTICAL);
setGravity(Gravity.CENTER);
//设置点击时弹出输入法
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
InputMethodManager imm =
(InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(CodeInputLayout.this, InputMethodManager.SHOW_IMPLICIT);
}
});
this.setOnKeyListener(new MyKeyListener());//按键监听
setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
LinearLayout lLayout = (LinearLayout) getChildAt(lInputIndex);
if (lLayout != null) {
CodeInputView pView = (CodeInputView) lLayout.getChildAt(inputIndex);
if (b) {
if (pView != null) {
pView.setmIsShowRemindLine(mIsShowInputLine);
pView.startInputState();
}
} else {
if (pView != null) {
pView.setmIsShowRemindLine(false);
pView.updateInputState(false);
}
}
}
}
});
}
/**
* 添加子View
*
* @param context
*/
private void addChildVIews(Context context) {
for (int j = 0; j < 4; j++) {
LinearLayout linearLayout = new LinearLayout(context);
LayoutParams lParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams
.WRAP_CONTENT);
linearLayout.setOrientation(HORIZONTAL);
if (j > 0) { //第一个和最后一个子View不添加边距
lParams.topMargin = mInterval;
}
for (int i = 0; i < maxLength; i++) {
CodeInputView codeInputView = new CodeInputView(context);
if(j==0 && i == 0){
codeInputView.setmIsShowRemindLine(true);
codeInputView.startInputState();
}
LayoutParams params = new LayoutParams(mItemWidth, mItemHeight);
if (i > 0) { //第一个和最后一个子View不添加边距
params.leftMargin = 0;
}
codeInputView.setInputStateColor(mInputColor);
codeInputView.setNoinputColor(mNoinputColor);
codeInputView.setInputStateTextColor(mTxtInputColor);
codeInputView.setRemindLineColor(mLineColor);
codeInputView.setmBoxDrawType(mDrawType);
codeInputView.setmShowPassType(mShowPassType);
codeInputView.setmDrawTxtSize(mTxtSize);
codeInputView.setmDrawBoxLineSize(mBoxLineSize);
codeInputView.setmIsShowRemindLine(mIsShowInputLine);
linearLayout.addView(codeInputView, params);
// addView(passWordView, params);
}
addView(linearLayout, lParams);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() == 0) { //判断 子View宽+边距是否超过了父布局 超过了则重置宽高
if ((maxLength * mItemWidth + (maxLength - 1) * mInterval) > getMeasuredWidth()) {
mItemWidth = (getMeasuredWidth() - (maxLength - 1) * mInterval) / maxLength;
mItemHeight = mItemWidth;
}
addChildVIews(getContext());
}
}
/**
* 添加
*
* @param pwd
*/
public void addPwd(String pwd) {
//maxLength*4 是因为一行有maxLen个一共4行
if (mPassList != null && mPassList.size() < maxLength * 4) {
mPassList.add(pwd + "");
setNextInput(pwd);
}
if (pwdChangeListener != null) {
if (mPassList.size() < maxLength) {
pwdChangeListener.onChange(getPassString());
} else {
pwdChangeListener.onFinished(getPassString());
}
}
}
/**
* 删除
*/
public void removePwd() {
if (mPassList != null && mPassList.size() > 0) {
mPassList.remove(mPassList.size() - 1);
setPreviosInput();
}
if (pwdChangeListener != null) {
if (mPassList.size() > 0) {
pwdChangeListener.onChange(getPassString());
} else {
pwdChangeListener.onNull();
}
}
}
/**
* 获取
*
* @return pwd
*/
public String getPassString() {
StringBuffer passString = new StringBuffer();
for (String i : mPassList) {
passString.append(i);
}
return passString.toString();
}
/**
* 设置下一个View为输入状态
*/
private void setNextInput(String pwdTxt) {
if (lInputIndex < 4) {
setNoInput(inputIndex, true, pwdTxt);
inputIndex++;
if (inputIndex >= maxLength && lInputIndex < 3) {
lInputIndex += 1;
inputIndex = 0;
}
setViewInputState(pwdTxt, false, false);
}
}
private void setViewInputState(String pwdTxt, boolean isUpData, boolean isInput) {
LinearLayout lLayout = (LinearLayout) getChildAt(lInputIndex);
if (lLayout != null) {
CodeInputView pView = (CodeInputView) lLayout.getChildAt(inputIndex);
if (pView != null) {
pView.setmPassText(pwdTxt + "");
if (isUpData) {
pView.updateInputState(isInput);
} else {
pView.startInputState();
}
}
}
}
/**
* 设置上一个View为输入状态
*/
private void setPreviosInput() {
if (inputIndex > 0) {
setNoInput(inputIndex, false, "");
inputIndex--;
setViewInputState("", false, false);
} else if (inputIndex == 0) {
if (lInputIndex == 0 && inputIndex == 0) {
setViewInputState("", false, false);
} else {
setNoInput(inputIndex, false, "");
inputIndex = 3;
lInputIndex--;
setViewInputState("", false, false);
}
}
}
/**
* 设置指定View为不输入状态
*
* @param index view下标
* @param isinput 是否输入过密码
*/
public void setNoInput(int index, boolean isinput, String txt) {
if (index < 0) {
return;
}
setViewInputState(txt, true, isinput);
}
public interface pwdChangeListener {
void onChange(String pwd);//密码改变
void onNull(); //密码删除为空
void onFinished(String pwd);//密码长度已经达到最大值
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.inputType = InputType.TYPE_CLASS_NUMBER; //显示数字键盘
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI;
return new ZanyInputConnection(this, false);
}
private class ZanyInputConnection extends BaseInputConnection {
@Override
public boolean commitText(CharSequence txt, int newCursorPosition) {
return super.commitText(txt, newCursorPosition);
}
public ZanyInputConnection(View targetView, boolean fullEditor) {
super(targetView, fullEditor);
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
return super.sendKeyEvent(event);
}
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
if (beforeLength == 1 && afterLength == 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);
}
}
/**
* 按键监听器
*/
class MyKeyListener implements OnKeyListener {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (event.isShiftPressed()) {//处理*#等键
return false;
}
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {//处理数字
addPwd(keyCode - 7 + ""); //点击添加密码
return true;
}
if (keyCode == KeyEvent.KEYCODE_DEL) { //点击删除
removePwd();
return true;
}
InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
return true;
}
return false;
}//onKey
}
//恢复状态
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
this.mPassList = savedState.saveString;
inputIndex = mPassList.size();
if (mPassList.isEmpty()) {
return;
}
for (int i = 0; i < getChildCount(); i++) {
CodeInputView codeInputView = (CodeInputView) getChildAt(i);
if (i > mPassList.size() - 1) {
if (codeInputView != null) {
codeInputView.setmIsShowRemindLine(false);
codeInputView.updateInputState(false);
}
break;
}
if (codeInputView != null) {
codeInputView.setmPassText(mPassList.get(i));
codeInputView.updateInputState(true);
}
}
}
//保存状态
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.saveString = this.mPassList;
return savedState;
}
public static class SavedState extends BaseSavedState {
public List<String> saveString;
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeList(saveString);
}
private SavedState(Parcel in) {
super(in);
in.readStringList(saveString);
}
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
布局文件里面调用:
<xxxx.goods.CodeInputLayout
android:id="@+id/passLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_tipsinfo"
android:layout_marginTop="@dimen/dp54"
app:box_draw_type="rect"
app:interval_width="4dp"
app:item_height="35dp"
app:item_width="35dp"
app:pass_inputed_type="text"
app:pass_leng="four" />
代码里面添加监听:
codeInputLayout.setPwdChangeListener(new CodeInputLayout.pwdChangeListener() {
@Override
public void onChange(String pwd) {
Log.i("info","onChange---in--"+pwd);
/* if(pwd.length()){
}*/
}
@Override
public void onNull() {
}
@Override
public void onFinished(String pwd) {
Log.i("info","onFinished--in---"+pwd);
codeStrg = pwd;
}
});