参考来自YU SplitEdittextView
如上效果图,有三种自定义输入框:
输入的内容也有两种形式:
在自定义attr.xml文件中中定义了一些属性属性。
无非就是一些字体颜色、字体大小、输入框类型、输入框内容展示形式等,详情可以看代码注释。
下边开始上代码
private void initAttrs(Context c, AttributeSet attrs) {
TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.SplitEditTextView);
mBorderSize = array.getDimension(R.styleable.SplitEditTextView_borderSize, dp2px(1f));
mBorderColor = array.getColor(R.styleable.SplitEditTextView_borderColor, Color.BLACK);
mCornerSize = array.getDimension(R.styleable.SplitEditTextView_cornerSize, 0f);
mDivisionLineSize = array.getDimension(R.styleable.SplitEditTextView_divisionLineSize, dp2px(1f));
mDivisionColor = array.getColor(R.styleable.SplitEditTextView_divisionLineColor, Color.BLACK);
mCircleRadius = array.getDimension(R.styleable.SplitEditTextView_circleRadius, dp2px(5f));
mContentNumber = array.getInt(R.styleable.SplitEditTextView_contentNumber, 6);
mContentShowMode = array.getInteger(R.styleable.SplitEditTextView_contentShowMode, CONTENT_SHOW_MODE_PASSWORD);
mInputBoxStyle = array.getInteger(R.styleable.SplitEditTextView_inputBoxStyle, INPUT_BOX_STYLE_CONNECT);
mSpaceSize = array.getDimension(R.styleable.SplitEditTextView_spaceSize, dp2px(10f));
mTextSize = array.getDimension(R.styleable.SplitEditTextView_android_textSize, sp2px(16f));
mTextColor = array.getColor(R.styleable.SplitEditTextView_android_textColor, Color.BLACK);
mInputBoxSquare = array.getBoolean(R.styleable.SplitEditTextView_inputBoxSquare, true);
mCursorColor = array.getColor(R.styleable.SplitEditTextView_cursorColor, Color.BLACK);
mCursorDuration = array.getInt(R.styleable.SplitEditTextView_cursorDuration, 500);
mCursorWidth = array.getDimension(R.styleable.SplitEditTextView_cursorWidth, dp2px(2f));
mCursorHeight = (int) array.getDimension(R.styleable.SplitEditTextView_cursorHeight, 0);
mUnderlineNormalColor = array.getInt(R.styleable.SplitEditTextView_underlineNormalColor, Color.BLACK);
mUnderlineFocusColor = array.getInt(R.styleable.SplitEditTextView_underlineFocusColor, 0);
array.recycle();
init();
}
private void init() {
mPaintBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintBorder.setStyle(Paint.Style.STROKE);
mPaintBorder.setStrokeWidth(mBorderSize);
mPaintBorder.setColor(mBorderColor);
mPaintDivisionLine = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintDivisionLine.setStyle(Paint.Style.STROKE);
mPaintDivisionLine.setStrokeWidth(mDivisionLineSize);
mPaintDivisionLine.setColor(mDivisionColor);
mPaintContent = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintContent.setTextSize(mTextSize);
mPaintCursor = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintCursor.setStrokeWidth(mCursorWidth);
mPaintCursor.setColor(mCursorColor);
mPaintUnderline = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintUnderline.setStrokeWidth(mBorderSize);
mPaintUnderline.setColor(mUnderlineNormalColor);
//单个输入框样式的RectF
mRectFSingleBox = new RectF();
//绘制Connect样式的矩形框
mRectFConnect = new RectF();
//设置单行输入
setSingleLine();
//EditText默认是获取焦点的
setFocusableInTouchMode(true);
//取消默认的光标
setCursorVisible(false);
//设置InputFilter,设置输入的最大字符长度为设置的长度
setFilters(new InputFilter[]{new InputFilter.LengthFilter(mContentNumber)});
}
@Override
protected void onDraw(Canvas canvas) {
//绘制输入框
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_SINGLE:
drawSingleStyle(canvas);
break;
case INPUT_BOX_STYLE_UNDERLINE:
drawUnderlineStyle(canvas);
break;
case INPUT_BOX_STYLE_CONNECT:
default:
drawConnectStyle(canvas);
break;
}
//绘制输入框内容
drawContent(canvas);
//绘制焦点光标
drawCursor(canvas);
}
/**
* 绘制矩形外框
*/
private void drawConnectStyle(Canvas canvas) {
//每次重新绘制时,先将rectF重置下
mRectFConnect.setEmpty();
//需要减去边框的一半
mRectFConnect.set(
mBorderSize / 2,
mBorderSize / 2,
getWidth() - mBorderSize / 2,
getHeight() - mBorderSize / 2
);
canvas.drawRoundRect(mRectFConnect, mCornerSize, mCornerSize, mPaintBorder);
//绘制分割线
drawDivisionLine(canvas);
}
/**
* 分割线条数为内容框数目-1
* 这里startX应该要加上左侧边框的宽度
* 应该还需要加上分割线的一半
* 至于startY和stopY不是 mBorderSize/2 而是 mBorderSize
* startX是计算整个宽度的,需要算上左侧的边框宽度,所以不是+mBorderSize/2 而是+mBorderSize
* startY和stopY:分割线是紧挨着边框内部的,所以应该是mBorderSize,而不是mBorderSize/2
*/
private void drawDivisionLine(Canvas canvas) {
float stopY = getHeight() - mBorderSize;
for (int i = 0; i < mContentNumber - 1; i++) {
//对于分割线条,startX = stopX
float startX = (i + 1) * getContentItemWidth() + i * mDivisionLineSize + mBorderSize + mDivisionLineSize / 2;
canvas.drawLine(startX, mBorderSize, startX, stopY, mPaintDivisionLine);
}
}
这里需要注意的是在绘制矩形以及其它图形的时候,矩形(图形)的边界是边框的中心,不是边框的边界,所以绘制矩形时的l,t,r,b四个参数都是去掉了边框宽度的一半(mBorderSize/2),不然会有误差。共勉!
/**
* 计算3种样式下,相应每个字符item的宽度
*/
private float getContentItemWidth() {
//计算每个密码字符所占的宽度,每种输入框样式下,每个字符item所占宽度也不一样
float tempWidth;
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_SINGLE:
//单个输入框样式:宽度-间距宽度(字符数-1)*每个间距宽度-每个输入框的左右边框宽度
tempWidth = getWidth() - (mContentNumber - 1) * mSpaceSize - 2 * mContentNumber * mBorderSize;
break;
case INPUT_BOX_STYLE_UNDERLINE:
//下划线样式:宽度-间距宽度(字符数-1)*每个间距宽度
tempWidth = getWidth() - (mContentNumber - 1) * mSpaceSize;
break;
case INPUT_BOX_STYLE_CONNECT:
//矩形输入框样式:宽度-左右两边框宽度-分割线宽度(字符数-1)*每个分割线宽度
default:
tempWidth = getWidth() - (mDivisionLineSize * (mContentNumber - 1)) - 2 * mBorderSize;
break;
}
return tempWidth / mContentNumber;
}
/**
* 绘制分割的方形输入框
*/
private void drawSingleStyle(Canvas canvas) {
for (int i = 0; i < mContentNumber; i++) {
mRectFSingleBox.setEmpty();
float left = i * getContentItemWidth() + i * mSpaceSize + i * mBorderSize * 2 + mBorderSize / 2;
float right = i * mSpaceSize + (i + 1) * getContentItemWidth() + (i + 1) * 2 * mBorderSize - mBorderSize / 2;
//为避免在onDraw里面创建RectF对象,这里使用rectF.set()方法
mRectFSingleBox.set(left, mBorderSize / 2, right, getHeight() - mBorderSize / 2);
canvas.drawRoundRect(mRectFSingleBox, mCornerSize, mCornerSize, mPaintBorder);
}
}
/**
* 绘制下划线式de输入框
* 线条起点startX:每个字符所占宽度itemWidth + 每个字符item之间的间距mSpaceSize
* 线条终点stopX:stopX与startX之间就是一个itemWidth的宽度
*/
private void drawUnderlineStyle(Canvas canvas) {
String content = getText().toString().trim();
for (int i = 0; i < mContentNumber; i++) {
//计算绘制下划线的startX
float startX = i * getContentItemWidth() + i * mSpaceSize;
//stopX
float stopX = getContentItemWidth() + startX;
//对于下划线这种样式,startY = stopY
float startY = getHeight() - mBorderSize / 2;
//这里判断是否设置有输入框获取焦点时,下划线的颜色
if (mUnderlineFocusColor != 0) {
if (content.length() >= i) {
mPaintUnderline.setColor(mUnderlineFocusColor);
} else {
mPaintUnderline.setColor(mUnderlineNormalColor);
}
}
canvas.drawLine(startX, startY, stopX, startY, mPaintUnderline);
}
}
/**
* 根据输入内容显示模式,绘制内容是密码类型的还是明文文本
*/
private void drawContent(Canvas canvas) {
int cy = getHeight() / 2;
String password = getText().toString().trim();
if (mContentShowMode == CONTENT_SHOW_MODE_PASSWORD) {
mPaintContent.setColor(Color.BLACK);
for (int i = 0; i < password.length(); i++) {
float startX = getDrawContentStartX(i);
canvas.drawCircle(startX, cy, mCircleRadius, mPaintContent);
}
} else {
mPaintContent.setColor(mTextColor);
//计算baseline
float baselineText = getTextBaseline(mPaintContent, cy);
for (int i = 0; i < password.length(); i++) {
float startX = getDrawContentStartX(i);
//计算文字宽度
String text = String.valueOf(password.charAt(i));
float textWidth = mPaintContent.measureText(text);
//绘制文字x应该还需要减去文字宽度的一半
canvas.drawText(text, startX - textWidth / 2, baselineText, mPaintContent);
}
}
}
/**
* 计算绘制文本的基线
* 固定方法
* @param paint 绘制文字的画笔
* @param halfHeight 高度的一半
*/
private float getTextBaseline(Paint paint, float halfHeight) {
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
return halfHeight + dy;
}
计算三种输入框样式下绘制圆和文字的x坐标
/*
* 计算三种输入框样式下绘制圆和文字的x坐标
*/
private float getDrawContentStartX(int index) {
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_SINGLE:
//单个输入框样式下的startX
//即 itemWidth/2 + i*itemWidth + i*每一个间距宽度 + 前面所有的左右边框
// i = 0,左侧1个边框
// i = 1,左侧3个边框(一个完整的item的左右边框+ 一个左侧边框)
// i = ..., (2*i+1)*mBorderSize
return getContentItemWidth() / 2 + index * getContentItemWidth() + index * mSpaceSize + (2 * index + 1) * mBorderSize;
case INPUT_BOX_STYLE_UNDERLINE:
//下划线输入框样式下的startX
//即 itemWidth/2 + i*itemWidth + i*每一个间距宽度
return getContentItemWidth() / 2 + index * mSpaceSize + index * getContentItemWidth();
case INPUT_BOX_STYLE_CONNECT:
//矩形输入框样式下的startX
//即 itemWidth/2 + i*itemWidth + i*分割线宽度 + 左侧的一个边框宽度
default:
return getContentItemWidth() / 2 + index * getContentItemWidth() + index * mDivisionLineSize + mBorderSize;
}
}
/**
* 绘制光标
* 光标只有一个,所以不需要根据循环来绘制,只需绘制第N个就行
* 即:
* 当输入内容长度为0,光标在第0个位置
* 当输入内容长度为1,光标应在第1个位置
* ...
* 所以光标所在位置为输入内容的长度
* 这里光标的长度默认就是 height/2
*/
private void drawCursor(Canvas canvas) {
if (mCursorHeight > getHeight()) {
throw new InflateException("cursor height must smaller than view height");
}
String content = getText().toString().trim();
float startX = getDrawContentStartX(content.length());
//如果设置得有光标高度,那么startY = (高度-光标高度)/2+边框宽度
if (mCursorHeight == 0) {
mCursorHeight = getHeight() / 2;
}
int sy = (getHeight() - mCursorHeight) / 2;
float startY = sy + mBorderSize;
float stopY = getHeight() - sy - mBorderSize;
//此时的绘制光标竖直线,startX = stopX
canvas.drawLine(startX, startY, startX, stopY, mPaintCursor);
}
/**
* 光标Runnable
* 通过Runnable每500ms执行重绘,每次runnable通过改变画笔的alpha值来使光标产生闪烁的效果
*/
private class CursorRunnable implements Runnable {
@Override
public void run() {
//获取光标画笔的alpha值
int alpha = mPaintCursor.getAlpha();
//设置光标画笔的alpha值
mPaintCursor.setAlpha(alpha == 0 ? 255 : 0);
invalidate();
postDelayed(this, mCursorDuration);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
cursorRunnable = new CursorRunnable();
postDelayed(cursorRunnable, mCursorDuration);
}
@Override
protected void onDetachedFromWindow() {
removeCallbacks(cursorRunnable);
super.onDetachedFromWindow();
}
/**
* 通过复写onTextChanged来实现对输入的监听
* 如果在onDraw里面监听text的输入长度来实现,会重复的调用该方法,就不妥当
*/
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
String content = text.toString().trim();
if (inputListener != null) {
if (content.length() == mContentNumber) {
inputListener.onInputFinished(content);
} else {
inputListener.onInputChanged(content);
}
}
}
/**
* 输入的监听抽象类
*/
public abstract class OnInputListener {
/**
* 输入完成的抽象方法
* @param content 输入内容
*/
public abstract void onInputFinished(String content);
/**
* 输入的内容
* 定义一个空实现方法,让用户选择性的复写该方法,需要就复写,不需要就不用重写
*/
public void onInputChanged(String text){
}
}
测量view宽高,让每个输入框都是正方形的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mInputBoxSquare) {
int width = MeasureSpec.getSize(widthMeasureSpec);
//计算view高度,使view高度和每个item的宽度相等,确保每个item是一个正方形
float itemWidth = getContentItemWidthOnMeasure(width);
switch (mInputBoxStyle) {
case INPUT_BOX_STYLE_UNDERLINE:
setMeasuredDimension(width, (int) (itemWidth + mBorderSize));
break;
case INPUT_BOX_STYLE_SINGLE:
case INPUT_BOX_STYLE_CONNECT:
default:
setMeasuredDimension(width, (int) (itemWidth + mBorderSize * 2));
break;
}
}
}
/**
* 设置密码是否可见
*/
public void setContentShowMode(int mode) {
if (mode != CONTENT_SHOW_MODE_PASSWORD && mode != CONTENT_SHOW_MODE_TEXT) {
throw new IllegalArgumentException(
"the value of the parameter must be one of" +
"{1:EDIT_SHOW_MODE_PASSWORD} or " +
"{2:EDIT_SHOW_MODE_TEXT}"
);
}
mContentShowMode = mode;
invalidate();
}
/**
* 设置输入框样式
*/
public void setInputBoxStyle(int inputBoxStyle) {
if (inputBoxStyle != INPUT_BOX_STYLE_CONNECT
&& inputBoxStyle != INPUT_BOX_STYLE_SINGLE
&& inputBoxStyle != INPUT_BOX_STYLE_UNDERLINE
) {
throw new IllegalArgumentException(
"the value of the parameter must be one of" +
"{1:INPUT_BOX_STYLE_CONNECT}, " +
"{2:INPUT_BOX_STYLE_SINGLE} or " +
"{3:INPUT_BOX_STYLE_UNDERLINE}"
);
}
mInputBoxStyle = inputBoxStyle;
requestLayout();
}
//设置监听
public void setOnInputListener(OnInputListener listener) {
this.inputListener = listener;
}
使用方法:
mSplitEditTextView = findViewById(R.id.splitTextView_single);
mSplitEditTextView1 = findViewById(R.id.splitTextView_line);
mSplitEditTextView2 = findViewById(R.id.splitTextView_whole);
mSplitEditTextView.setInputBoxStyle(SplitEditTextView.INPUT_BOX_STYLE_SINGLE);
mSplitEditTextView1.setInputBoxStyle(SplitEditTextView.INPUT_BOX_STYLE_UNDERLINE);
mSplitEditTextView2.setInputBoxStyle(SplitEditTextView.INPUT_BOX_STYLE_CONNECT);
mSplitEditTextView.setContentShowMode(SplitEditTextView.CONTENT_SHOW_MODE_TEXT);
mSplitEditTextView1.setContentShowMode(SplitEditTextView.CONTENT_SHOW_MODE_TEXT);
mSplitEditTextView2.setContentShowMode(SplitEditTextView.CONTENT_SHOW_MODE_TEXT);
mSplitEditTextView.setOnInputListener(new OnInputListener() {
@Override
public void onInputFinished(String content) {
}
@Override
public void onInputChanged(String text) {
super.onInputChanged(text);
}
});
也可以通过xml实现一些
/>
可通过xml实现的一些属性值
属性名称 属性说明 默认值
borderSize 边框宽度 1dp
borderColor 边框颜色 Color.BLACK
conerSize 边框圆角大小 0
divisionLineSize 分割线宽度 1dp
divisionLineColor 分割线颜色 Color.BLACK
circleRadius 实心圆半径 5dp
contentNumber 输入内容数量 6
contentShowMode 内容显示模式 password
spaceSize 输入框间距 10dp
inputBoxStyle 输入框样式 connectBox
inputBoxSquare 输入框是否正方形 true
cursorWidth 光标宽度 2dp
cursorHeight 光标高度 输入框高度的一半
cursorColor 光标颜色 Color.BLACK
cursorDuration 闪烁时长 500
underlineNormalColor 下划线normal颜色 Color.BLACK
underlineFocusColor 下划线focus颜色 未设置
源码在此,还望各位大佬批评指正哈