Android仿淘宝密码输入框功能
背景
效果图

挽起袖子撸代码
- 密码框的java代码: 
public class PasswordEditText extends EditText {/** 默认的密码颜色 */private static final int DEFAULT_PASSWORD_COLOR = Color.parseColor("#333333");/** 默认边框的颜色 */private static final int DEFAULT_BORDER_COLOR = Color.parseColor("#d1d2d6");/** 默认密码下划线的颜色 */private static final int DEFAULT_UNDERLINE_COLOR = Color.parseColor("#666666");private static final int BACKGROUND_STYLE_BORDER = 0;private static final int BACKGROUND_STYLE_UNDERLINE = 1;/** 密码的画笔 */private Paint mPasswordPaint;/** 密码圆点的颜色 */private int mPasswordColor = DEFAULT_PASSWORD_COLOR;/** 一个密码所占的宽度 */private int mPasswordItemWidth;/** 密码的个数,默认为4位数 */private int mPasswordNumber = 4;/** 密码圆点的半径大小,m默认为4像素 */private int mPasswordRadius = 4;/** 下划线的画笔 */private Paint mUnderlinePaint;/** 密码底部下划线的宽度 */private int mUnderlineWidth;/** 密码底部下划线的厚度 */private int mUnderlineSize = 1;/** 密码底部下划线的宽度 */private int mUnderlineColor = DEFAULT_UNDERLINE_COLOR;/** 边框的画笔 */private Paint mBorderPaint;/** 背景边框颜色 */private int mBorderColor = DEFAULT_BORDER_COLOR;/** 背景边框厚度大小 */private int mBorderStrokeSize = 1;/** 背景边框圆角大小 */private int mBorderCorner = 0;/** 分隔线的画笔 */private Paint mDivisionLinePaint;/** 分割线的颜色,默认跟边框同个颜色 */private int mDivisionLineColor = mBorderColor;/** 分割线的大小 */private int mDivisionLineSize = 1;/** 样式类型 */private int mBgStyle = 0;public PasswordEditText(Context context) {this(context, null);}public PasswordEditText(Context context, AttributeSet attrs) {super(context, attrs);initAttributeSet(context, attrs);initPaint();// 默认只能够设置数字setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);}/*** 初始化画笔*/private void initPaint() {// 初始化密码边框的画笔mBorderPaint = new Paint();// 抗锯齿mBorderPaint.setAntiAlias(true);// 防抖动mBorderPaint.setDither(true);// 给画笔设置大小mBorderPaint.setStrokeWidth(mBorderStrokeSize);// 设置背景的颜色mBorderPaint.setColor(mBorderColor);// 画空心mBorderPaint.setStyle(Paint.Style.STROKE);// 初始化分隔线的画笔mDivisionLinePaint = new Paint();// 抗锯齿mDivisionLinePaint.setAntiAlias(true);// 防抖动mDivisionLinePaint.setDither(true);// 分割线画笔设置大小mDivisionLinePaint.setStrokeWidth(mDivisionLineSize);// 设置分割线的颜色mDivisionLinePaint.setColor(mDivisionLineColor);//初始化密码的画笔mPasswordPaint = new Paint();// 抗锯齿mPasswordPaint.setAntiAlias(true);// 防抖动mPasswordPaint.setDither(true);// 密码绘制是实心mPasswordPaint.setStyle(Paint.Style.FILL);// 设置密码的颜色mPasswordPaint.setColor(mPasswordColor);//初始化下划线的画笔mUnderlinePaint = new Paint();// 抗锯齿mUnderlinePaint.setAntiAlias(true);// 防抖动mUnderlinePaint.setDither(true);// 设置颜色mUnderlinePaint.setColor(mUnderlineColor);// 设置画笔的大小mUnderlinePaint.setStrokeWidth(mUnderlineSize);}/*** 初始化属性*/private void initAttributeSet(Context context, AttributeSet attrs) {TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PasswordEditText);// 密码的颜色mPasswordColor = array.getColor(R.styleable.PasswordEditText_passwordColor, mPasswordColor);// 密码圆点的半径mPasswordRadius = (int) array.getDimension(R.styleable.PasswordEditText_passwordRadius,dip2px(mPasswordRadius));// 密码的个数mPasswordNumber = array.getInteger(R.styleable.PasswordEditText_passwordNumber,mPasswordNumber);// 间隔线大小mDivisionLineSize = (int) array.getDimension(R.styleable.PasswordEditText_divisionLineSize,dip2px(mDivisionLineSize));// 间隔线的颜色mDivisionLineColor = array.getColor(R.styleable.PasswordEditText_divisionLineColor,mDivisionLineColor);// 边框的厚度mBorderStrokeSize = (int) array.getDimension(R.styleable.PasswordEditText_bgSize, dip2px(mBorderStrokeSize));// 边框的圆角mBorderCorner = (int) array.getDimension(R.styleable.PasswordEditText_bgCorner, 0);// 获取边框的颜色mBorderColor = array.getColor(R.styleable.PasswordEditText_bgColor, mBorderColor);// 下划线的颜色mUnderlineColor = array.getColor(R.styleable.PasswordEditText_underlineColor, mUnderlineColor);// 下划线的厚度mUnderlineSize = (int) array.getDimension(R.styleable.PasswordEditText_underlineSize,dip2px(mUnderlineSize));// 样式类型mBgStyle = array.getInteger(R.styleable.PasswordEditText_bgStyle, BACKGROUND_STYLE_BORDER);array.recycle();}/*** dip 转 px*/private float dip2px(int dip) {return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dip, getResources().getDisplayMetrics());}@Overrideprotected void onDraw(Canvas canvas) {// 一个密码的宽度mPasswordItemWidth =(getWidth() - 2 * mBorderStrokeSize - (mPasswordNumber - 1) * mDivisionLineSize)/ mPasswordNumber;mUnderlineWidth = mPasswordItemWidth - 8 * mBorderStrokeSize;if(mBgStyle == BACKGROUND_STYLE_UNDERLINE) {//绘制下划线drawUnderLine(canvas);}else {// 画背景drawBg(canvas);// 画分割线drawDivisionLine(canvas);}// 画密码drawPassword(canvas);// 当前密码是不是满了if (mListener != null) {String password = getText().toString().trim();if (password.length() >= mPasswordNumber) {mListener.passwordFull(password);} else {mListener.passwordChanged(password);}}}/*** 绘制密码*/private void drawPassword(Canvas canvas) {// 获取当前textString text = getText().toString().trim();// 获取密码的长度int passwordLength = text.length();// 不断的绘制密码for (int i = 0; i < passwordLength; i++) {int cy = getHeight() / 2;int cx = mBorderStrokeSize+ i * mPasswordItemWidth + i * mDivisionLineSize + mPasswordItemWidth / 2;canvas.drawCircle(cx, cy, mPasswordRadius, mPasswordPaint);}}/*** 绘制分割线*/private void drawDivisionLine(Canvas canvas) {for (int i = 0; i < mPasswordNumber - 1; i++) {int startX = mBorderStrokeSize + (i + 1) * mPasswordItemWidth + i * mDivisionLineSize;int startY = mBorderStrokeSize;int endX = startX;int endY = getHeight() - mBorderStrokeSize;canvas.drawLine(startX, startY, endX, endY, mDivisionLinePaint);}}/*** 绘制背景*/private void drawBg(Canvas canvas) {RectF rect = new RectF(mBorderStrokeSize,mBorderStrokeSize, getWidth() - mBorderStrokeSize, getHeight() - mBorderStrokeSize);// 绘制背景 drawRect , drawRoundRect ,// 如果有圆角那么就绘制drawRoundRect,否则绘制drawRectif (mBorderCorner == 0) {canvas.drawRect(rect, mBorderPaint);} else {canvas.drawRoundRect(rect, mBorderCorner, mBorderCorner, mBorderPaint);}}/*** 绘制每个密码项的底部下划线*/private void drawUnderLine(Canvas canvas) {for (int i = 0; i < mPasswordNumber; i++) {int startX = mBorderStrokeSize * 4 + i * mPasswordItemWidth;int startY = getHeight() - mBorderStrokeSize;int endX = startX + mUnderlineWidth;int endY = getHeight() - mBorderStrokeSize;canvas.drawLine(startX, startY, endX, endY, mUnderlinePaint);}}/*** 添加一个密码*/public void addPassword(String number) {// 把之前的密码取出来String password = getText().toString().trim();if (password.length() >= mPasswordNumber) {// 密码不能超过当前密码个输return;}// 密码叠加password += number;setText(password);}/*** 删除最后一位密码*/public void deleteLastPassword() {String password = getText().toString().trim();// 判断当前密码是不是空if (TextUtils.isEmpty(password)) {return;}password = password.substring(0, password.length() - 1);setText(password);}// 设置当前密码是否已满的接口回掉private PasswordFullListener mListener;public void setOnPasswordFullListener(PasswordFullListener listener) {this.mListener = listener;}/*** 密码已经全部填满*/public interface PasswordFullListener {void passwordFull(String password);void passwordChanged(String password);}}
- 密码框的自定义属性 
- 定制的数字键盘的java代码: 
public class DigitKeyboard extends LinearLayout implements View.OnClickListener {private DigitKeyboardClickListener mListener;public DigitKeyboard(Context context) {this(context, null);}public DigitKeyboard(Context context, AttributeSet attrs) {this(context, attrs, 0);}public DigitKeyboard(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);inflate(context, R.layout.digit_keyboard, this);setChildViewOnclick(this);}/*** 设置键盘子View的点击事件*/private void setChildViewOnclick(ViewGroup parent) {int childCount = parent.getChildCount();for (int i = 0; i < childCount; i++) {// 不断的递归设置点击事件View view = parent.getChildAt(i);if (view instanceof ViewGroup) {setChildViewOnclick((ViewGroup) view);continue;}view.setOnClickListener(this);}}@Overridepublic void onClick(View v) {View clickView = v;if (clickView instanceof TextView) {// 如果点击的是TextViewString number = ((TextView) clickView).getText().toString();if (!TextUtils.isEmpty(number)) {if (mListener != null) {// 回调mListener.click(number);}}} else if (clickView instanceof ImageView) {// 如果是图片那肯定点击的是删除if (mListener != null) {mListener.delete();}}}public boolean dispatchKeyEventInFullScreen(KeyEvent event) {if(event == null){return false;}switch (event.getKeyCode()) {case KeyEvent.KEYCODE_BACK:if (isShown()) {setVisibility(GONE);return true;}default:return false;}}/*** 设置键盘的点击回调监听*/public void setOnDigitKeyboardClickListener(DigitKeyboardClickListener listener) {this.mListener = listener;}/*** 点击键盘的回调监听*/public interface DigitKeyboardClickListener {public void click(String number);public void delete();}}
- 键盘的布局文件:digit_keyboard.xml(这边用LinearLayout层级比较多,推荐可以使用GridView) 
android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingTop="1dp"android:background="#EBEBEB"android:orientation="vertical">android:layout_width="match_parent"android:layout_height="wrap_content">android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginRight="1dp"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="1" />android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginRight="1dp"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="2" />android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="3" />android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="1dp">android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginRight="1dp"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="4" />android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginRight="1dp"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="5" />android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="6" />android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="1dp">android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginRight="1dp"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="7" />android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginRight="1dp"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="8" />android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="9" />android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="1dp"android:orientation="horizontal">android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginRight="1dp"android:layout_weight="1"android:gravity="center"android:padding="20dp" />android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginRight="1dp"android:layout_weight="1"android:background="#FFFFFF"android:gravity="center"android:padding="20dp"android:text="0" />android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:padding="15dp"android:layout_gravity="center"android:src="@drawable/hkyb_keyboard_delete" />
使用
- 布局文件引用: 
xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent">android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/hykb_white">android:orientation="vertical"android:layout_width="match_parent"android:layout_height="44dp">android:id="@+id/btn_close"android:layout_width="wrap_content"android:layout_height="match_parent"android:paddingLeft="12dp"android:paddingRight="12dp"android:src="@drawable/ic_back_default"/>android:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginBottom="36dp"android:layout_marginLeft="24dp"android:layout_marginRight="24dp"android:layout_marginTop="38dp"android:lineSpacingExtra="4dp"android:textColor="@color/black"android:textSize="24sp"android:textStyle="bold"/>android:id="@+id/tv_content"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginBottom="30dp"android:layout_marginLeft="24dp"android:layout_marginRight="24dp"android:textColor="@color/gray"android:textSize="14sp"/>android:id="@+id/et_password"android:layout_width="240dp"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:background="@null"android:digits="0123456789"android:inputType="number"android:padding="10dp"app:bgCorner="3dp"app:passwordColor="@color/black"app:bgStyle="underline"app:underlineSize="2dp"app:underlineColor="@color/thin_gray"/>android:id="@+id/btn_sure"android:layout_width="match_parent"android:layout_height="44dp"android:layout_marginLeft="24dp"android:layout_marginRight="24dp"android:layout_marginTop="30dp"android:background="@drawable/bg_open_button"android:gravity="center"android:textColor="@color/hykb_white"android:textSize="16sp"/>android:id="@+id/tv_get_password"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:textColorHighlight="@android:color/transparent"android:layout_marginTop="20dp"android:visibility="gone"/>android:id="@+id/custom_key_board"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"/>
- java代码引用: 
public class PasswordActivity extends BaseActivityimplements DigitKeyboard.DigitKeyboardClickListener,PasswordEditText.PasswordFullListener {private PasswordEditText pwdEdit;private DigitKeyboard keyboard;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(ResourcesUtils.getLayoutId(this, "activity_password"));initView();initListener();}/*** 初始化控件*/private void initView() {pwdEdit = findViewById(ResourcesUtils.getId(this, "et_password"));keyboard = findViewById(ResourcesUtils.getId(this, "custom_key_board"));}/*** 初始化事件监听*/private void initListener() {keyboard.setOnDigitKeyboardClickListener(this);pwdEdit.setOnPasswordFullListener(this);pwdEdit.setEnabled(true);pwdEdit.setFocusable(false);pwdEdit.setFocusableInTouchMode(false);pwdEdit.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {keyboard.setVisibility(View.VISIBLE);}});}@Overridepublic void click(String number) {pwdEdit.addPassword(number);}@Overridepublic void delete() {pwdEdit.deleteLastPassword();}@Overridepublic void passwordFull(String password) {setButtonStatus(true);}@Overridepublic void passwordChanged(String password) {setButtonStatus(false);}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {boolean isConsum = keyboard.dispatchKeyEventInFullScreen(event);return isConsum ? isConsum : super.onKeyDown(keyCode, event);}
评论
