背景
近期公司项目需要实现青少年模式,UI上需要一个特定的密码输入框,类似淘宝和银行。
效果图
挽起袖子撸代码
- 密码框的java代码:
package com.xxxx.fcm.sdk.internal.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import com.xxxx.fcm.sdk.R;
/**
* Description:自定义密码输入框
*
* @author chenbaoyang
* @create 2020/4/11 10: 18
*/
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);
}
}
- 密码框的自定义属性
<declare-styleable name="PasswordEditText">
<!-- 密码的个数 -->
<attr name="passwordNumber" format="integer"/>
<!-- 密码圆点的半径 -->
<attr name="passwordRadius" format="dimension" />
<!-- 密码圆点的颜色 -->
<attr name="passwordColor" format="color" />
<!-- 下划线的厚度 -->
<attr name="underlineSize" format="dimension" />
<!-- 下划线的颜色 -->
<attr name="underlineColor" format="color" />
<!-- 分割线的颜色 -->
<attr name="divisionLineColor" format="color" />
<!-- 分割线的大小 -->
<attr name="divisionLineSize" format="color" />
<!-- 背景边框的颜色 -->
<attr name="bgColor" format="color" />
<!-- 背景边框的大小 -->
<attr name="bgSize" format="dimension" />
<!-- 背景边框的圆角大小 -->
<attr name="bgCorner" format="dimension"/>
<!-- -->
<attr name="bgStyle" format="integer" >
<enum name="border" value="0" />
<enum name="underline" value="1" />
</attr>
</declare-styleable>
- 定制的数字键盘的java代码:
package com.xxxx.fcm.sdk.internal.widget;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.xxxx.fcm.sdk.R;
/**
* Description:DigitKeyboard
*
* @author chenbaoyang
* @create 2020/4/11 10: 21
*/
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)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="1dp"
android:background="#EBEBEB"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
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" />
<TextView
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" />
<TextView
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" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp">
<TextView
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" />
<TextView
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" />
<TextView
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" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp">
<TextView
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" />
<TextView
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" />
<TextView
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" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="1dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="1dp"
android:layout_weight="1"
android:gravity="center"
android:padding="20dp" />
<TextView
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" />
<ImageView
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" />
</LinearLayout>
</LinearLayout>
使用
- 布局文件引用:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/hykb_white"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="44dp"
>
<ImageView
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"
/>
</LinearLayout>
<TextView
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"
/>
<TextView
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"
/>
<com.xxxx.fcm.sdk.internal.widget.PasswordEditText
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"
/>
<TextView
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"
/>
<TextView
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"
/>
</LinearLayout>
<com.xxxx.fcm.sdk.internal.widget.DigitKeyboard
android:id="@+id/custom_key_board"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
/>
</RelativeLayout>
- java代码引用:
package com.xxxx.fcm.sdk.internal.ui;
import com.xxxx.fcm.sdk.internal.widget.DigitKeyboard;
import com.xxxx.fcm.sdk.internal.widget.PasswordEditText;
/**
* Description:密码界面
*
* @author chenbaoyang
* @create 2020/4/11 10: 47
*/
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);
}
结语
这个密码框就是大概这么个玩意,欢迎大家指正错误。