在项目中有涉及到输入密码的地方并且UI已经给了相应的效果图,由于普通的EditTextView已经无法满足要求,所以只能自己造轮子了。
先看一张效果图
老规矩 还是动手前先理思路
~输入框嘛肯定得处理很多的按键响应事件 系统的EditTextView已经处理的够好了 所以得基于它来扩展
~得把一些输入框的默认样式替换成自己的
~把整个宽度等分成6份并计算出每个框框的x,y的位置并记录下来
~设置监听事件用来获取当前输入的内容和数量
~每当内容变化时动态的绘制成对应框框里的小圆点
~计算下一个要输入的索引位置并绘制出对应的待输入游标
自定义一个SafeEditorTextView类 继承至 AppCompatEditText 并把默认背景和默认游标给去掉
public class SafeEditorTextView extends AppCompatEditText {
public SafeEditorTextView(Context context) {
super(context);
}
public SafeEditorTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setBackground(null);
setCursorVisible(false);
}
}
定义和初始化所需的对象
// 边框画笔
private Paint mSide;
// 圆点画笔
private Paint mCircle;
// 游标线画笔
private Paint mLine;
// 用户输入的密码
private String mText = "";
// 输入内容的长度
private int mLength = 6;
// 框与框的间距
private int space = 30;
// 边框圆角度数
private int round = 5;
// 线条宽度
private int strokeWidth = 3;
// 存储边框临界值的容器
private List<RectF> rectFList ;
public SafeEditorTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mSide = new Paint(Paint.ANTI_ALIAS_FLAG);
mSide.setStyle(Paint.Style.STROKE);
mSide.setStrokeWidth(strokeWidth);
mCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
mCircle.setStyle(Paint.Style.FILL);
mCircle.setColor(Color.BLACK);
mLine = new Paint();
mLine.setColor(Color.BLACK);
mLine.setStyle(Paint.Style.FILL_AND_STROKE);
mLine.setStrokeWidth(strokeWidth);
setTextColor(Color.TRANSPARENT);
setInputType(InputType.TYPE_CLASS_NUMBER |InputType.TYPE_NUMBER_VARIATION_PASSWORD);
setFilters(new InputFilter[]{new InputFilter.LengthFilter(mLength)});
setBackground(null);
setCursorVisible(false);
}
接下来要在onMeasure中计算出每个框框的位置
思路是先获取到当前容器的总宽度supWidth,根据总宽度减去space*mLength-1得出totalSpace总间距的宽度,用supWidth减去totalSpace/mLength得出单个框框的宽度,高度默认使用的是容器的高度也就是supHeight,
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (rectFList == null){
rectFList = new ArrayList<>();
int height = getMeasuredHeight();
int width = getMeasuredWidth();
int singleWidth = (width - (mLength - 1) * space) / mLength;
}
}
有了单个边框的宽高接下来再来计算每个边框所在Rect的位置
假设当前边框的索引是i,i=0时那么边框的left位置就是i*singleWidth+strokeWidth,-、i>0时边框的left位置就是i * singleWidth + i * space,而它的right位置就是i * singleWidth + singleWidth + i * space,接下来看代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (rectFList == null){
rectFList = new ArrayList<>();
int height = getMeasuredHeight();
int width = getMeasuredWidth();
int singleWidth = (width - (mLength - 1) * space) / mLength;
for (int i = 0; i < mLength; i++) {
int left = i == 0 ? i * singleWidth + strokeWidth: i * singleWidth + i * space;
RectF rectF = new RectF(left, strokeWidth, i * singleWidth + singleWidth + i * space, height - strokeWidth);
rectFList.add(rectF);
}
}
}
计算完边框的rect接下来就要进行绘制了
先根据输入的length来设置不同边框的颜色,在将已经输入的文字使用圆来代替,最后绘制出下一个要输入的游标位置
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Log.d(TAG, "onDraw: " + mText.length());
for (int i = 0; i < mLength; i++) {
RectF rectF = rectFList.get(i);
// 设置需要高亮的边框颜色
if (i <= mText.length()){
mSide.setColor(Color.BLACK);
}else {
mSide.setColor(Color.LTGRAY);
}
// 绘制边框
canvas.drawRoundRect(rectF, round,round, mSide);
float singleWidth = rectF.right - rectF.left;
float singleHeight = rectF.bottom - rectF.top;
// 将已经输入的文字用圆代替
if (i < mText.length()){
canvas.drawCircle(rectF.left + singleWidth / 2, rectF.bottom - singleHeight / 2, singleWidth / 6, mCircle);
}
// 绘制游标线
if (i == mText.length()){
float lineScale = singleWidth / 4;
float startY = rectF.top + singleHeight / 4 * 3;
canvas.drawLine(rectF.left + lineScale, startY, rectF.right - lineScale, startY, mLine);
}
}
}
到这基本上就已经初具雏形了,剩下的要定义一个回调给使用者用来监听输入内容和状态使用
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
if (text.length() <= mLength){
mText = text.toString();
if (changeListener != null) changeListener.onChange(mText);
}
if (text.length() == mLength){
if (changeListener != null) changeListener.onFinish(mText);
}
}
public void setInputChangeListener(OnInputChangeListener changeListener) {
this.changeListener = changeListener;
}
public interface OnInputChangeListener{
void onFinish(@NonNull String text);
void onChange(@NonNull String text);
}
最后将最长长度mLength、间距space、线宽strokeWidth这些变量定义成自定义属性让使用者在xml中可以进行设置
<declare-styleable name="SafeEditorTextView">
<attr name="lenth" format="integer"/>
<attr name="android:strokeWidth"/>
<attr name="space" format="dimension"/>
</declare-styleable>
最后的最后贴下源码
/**
* 自定义密码输入框
*/
public class SafeEditorTextView extends AppCompatEditText {
public SafeEditorTextView(Context context) {
super(context);
}
public SafeEditorTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SafeEditorTextView);
mLength = array.getInt(R.styleable.SafeEditorTextView_lenth, 6);
strokeWidth = array.getDimensionPixelOffset(R.styleable.SafeEditorTextView_android_strokeWidth, 3);
space = array.getDimensionPixelOffset(R.styleable.SafeEditorTextView_space, 30);
array.recycle();
mSide = new Paint(Paint.ANTI_ALIAS_FLAG);
mSide.setStyle(Paint.Style.STROKE);
mSide.setStrokeWidth(strokeWidth);
mCircle = new Paint(Paint.ANTI_ALIAS_FLAG);
mCircle.setStyle(Paint.Style.FILL);
mCircle.setColor(Color.BLACK);
mLine = new Paint();
mLine.setColor(Color.BLACK);
mLine.setStyle(Paint.Style.FILL_AND_STROKE);
mLine.setStrokeWidth(strokeWidth);
setTextColor(Color.TRANSPARENT);
setInputType(InputType.TYPE_CLASS_NUMBER |InputType.TYPE_NUMBER_VARIATION_PASSWORD);
setFilters(new InputFilter[]{new InputFilter.LengthFilter(mLength)});
setBackground(null);
setLongClickable(false);
setTextIsSelectable(false);
setCursorVisible(false);
Log.d(TAG, "SafeEditorTextView: ");
}
// 边框画笔
private Paint mSide;
// 圆点画笔
private Paint mCircle;
// 游标线画笔
private Paint mLine;
// 用户输入的密码
private String mText = "";
// 最长长度
private int mLength = 6;
// 框与框的间距
private int space = 30;
// 边框圆角度数
private int round = 5;
// 线条宽度
private int strokeWidth = 3;
// 存储边框临界值的容器
private List<RectF> rectFList ;
private static final String TAG = "SafeEditorTextView";
// 输入变化的监听器
private OnInputChangeListener changeListener;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (rectFList == null){
rectFList = new ArrayList<>();
int height = getMeasuredHeight();
int width = getMeasuredWidth();
int singleWidth = (width - (mLength - 1) * space) / mLength;
for (int i = 0; i < mLength; i++) {
int left = i == 0 ? i * singleWidth + strokeWidth: i * singleWidth + i * space;
RectF rectF = new RectF(left, strokeWidth, i * singleWidth + singleWidth + i * space, height - strokeWidth);
rectFList.add(rectF);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Log.d(TAG, "onDraw: " + mText.length());
for (int i = 0; i < mLength; i++) {
RectF rectF = rectFList.get(i);
// 设置需要高亮的边框颜色
if (i <= mText.length()){
mSide.setColor(Color.BLACK);
}else {
mSide.setColor(Color.LTGRAY);
}
// 绘制边框
canvas.drawRoundRect(rectF, round,round, mSide);
float singleWidth = rectF.right - rectF.left;
float singleHeight = rectF.bottom - rectF.top;
// 将已经输入的文字用圆代替
if (i < mText.length()){
canvas.drawCircle(rectF.left + singleWidth / 2, rectF.bottom - singleHeight / 2, singleWidth / 6, mCircle);
}
// 绘制游标线
if (i == mText.length()){
float lineScale = singleWidth / 4;
float startY = rectF.top + singleHeight / 4 * 3;
canvas.drawLine(rectF.left + lineScale, startY, rectF.right - lineScale, startY, mLine);
}
}
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
if (text.length() <= mLength){
mText = text.toString();
if (changeListener != null) changeListener.onChange(mText);
}
if (text.length() == mLength){
// hideKeyboard();
if (changeListener != null) changeListener.onFinish(mText);
}
}
//隐藏软键盘并让editText失去焦点
private void hideKeyboard() {
clearFocus();
//这里先获取InputMethodManager再调用他的方法来关闭软键盘
//InputMethodManager就是一个管理窗口输入的manager
InputMethodManager im = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (im != null) {
im.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
public void setInputChangeListener(OnInputChangeListener changeListener) {
this.changeListener = changeListener;
}
public interface OnInputChangeListener{
void onFinish(@NonNull String text);
void onChange(@NonNull String text);
}
}