Android仿淘宝密码输入框功能

龙旋

共 11958字,需浏览 24分钟

 ·

2021-11-08 11:41

背景

近期公司项目需要实现青少年模式,UI上需要一个特定的密码输入框,类似淘宝和银行。

效果图


挽起袖子撸代码

  • 密码框的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()); }
@Override protected 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) { // 获取当前text String 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,否则绘制drawRect if (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); } }
@Override public void onClick(View v) { View clickView = v; if (clickView instanceof TextView) { // 如果点击的是TextView String 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 BaseActivity    implements DigitKeyboard.DigitKeyboardClickListener,    PasswordEditText.PasswordFullListener {
private PasswordEditText pwdEdit; private DigitKeyboard keyboard;
@Override protected 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() { @Override public void onClick(View v) { keyboard.setVisibility(View.VISIBLE); } }); }
@Override public void click(String number) { pwdEdit.addPassword(number); }
@Override public void delete() { pwdEdit.deleteLastPassword(); }
@Override public void passwordFull(String password) { setButtonStatus(true); }
@Override public void passwordChanged(String password) { setButtonStatus(false); }
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { boolean isConsum = keyboard.dispatchKeyEventInFullScreen(event); return isConsum ? isConsum : super.onKeyDown(keyCode, event); }


到这里就结束啦。
浏览 18
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报