最近在项目中需要用到支付页面,就仿支付宝支付页面自定义了一个密码输入框
1.自定义密码框View
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;
/**
* Email [email protected]
* Created by Darren on 2016/12/24.
* Version 1.0
* Description: 自定义输入密码框
*/
public class PsdEditText extends EditText {
// 画笔
private Paint mPaint;
// 一个密码所占的宽度
private int mPasswordItemWidth;
// 密码的个数默认为6位数
private int mPasswordNumber = 6;
// 背景边框颜色
private int mBgColor = Color.parseColor("#d1d2d6");
// 背景边框大小
private int mBgSize = 1;
// 背景边框圆角大小
private int mBgCorner = 0;
// 分割线的颜色
private int mDivisionLineColor = mBgColor;
// 分割线的大小
private int mDivisionLineSize = 1;
// 密码圆点的颜色
private int mPasswordColor = mDivisionLineColor;
// 密码圆点的半径大小
private int mPasswordRadius = 4;
public PsdEditText(Context context) {
this(context, null);
}
public PsdEditText(Context context, AttributeSet attrs) {
super(context, attrs);
initAttributeSet(context, attrs);
initPaint();
// 默认只能够设置数字和字母
setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
}
/**
* 初始化画笔
*/
private void initPaint() {
mPaint = new Paint();
// 抗锯齿
mPaint.setAntiAlias(true);
// 防抖动
mPaint.setDither(true);
}
/**
* 初始化属性
*/
private void initAttributeSet(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PsdEditText);
// 获取大小
mDivisionLineSize = (int) array.getDimension(R.styleable.PsdEditText_divisionLineSize, dip2px(mDivisionLineSize));
mPasswordRadius = (int) array.getDimension(R.styleable.PsdEditText_passwordRadius, dip2px(mPasswordRadius));
mBgSize = (int) array.getDimension(R.styleable.PsdEditText_bgSize, dip2px(mBgSize));
mBgCorner = (int) array.getDimension(R.styleable.PsdEditText_bgCorner, 0);
// 获取颜色
mBgColor = array.getColor(R.styleable.PsdEditText_bgColor, mBgColor);
mDivisionLineColor = array.getColor(R.styleable.PsdEditText_divisionLineColor, mDivisionLineColor);
mPasswordColor = array.getColor(R.styleable.PsdEditText_passwordColor, mDivisionLineColor);
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 * mBgSize - (mPasswordNumber - 1) * mDivisionLineSize) / mPasswordNumber;
// 画背景
drawBg(canvas);
// 画分割线
drawDivisionLine(canvas);
// 画密码
drawPassword(canvas);
// 当前密码是不是满了
if(mListener != null){
String password = getText().toString().trim();
if(password.length()>=mPasswordNumber){
mListener.passwordFull(password);
}
}
}
/**
* 绘制密码
*/
private void drawPassword(Canvas canvas) {
// 密码绘制是实心
mPaint.setStyle(Paint.Style.FILL);
// 设置密码的颜色
mPaint.setColor(mPasswordColor);
// 获取当前text
String text = getText().toString().trim();
// 获取密码的长度
int passwordLength = text.length();
// 不断的绘制密码
for (int i = 0; i < passwordLength; i++) {
int cy = getHeight() / 2;
int cx = mBgSize + i * mPasswordItemWidth + i * mDivisionLineSize + mPasswordItemWidth / 2;
canvas.drawCircle(cx, cy, mPasswordRadius, mPaint);
}
}
/**
* 绘制分割线
*/
private void drawDivisionLine(Canvas canvas) {
// 给画笔设置大小
mPaint.setStrokeWidth(mDivisionLineSize);
// 设置分割线的颜色
mPaint.setColor(mDivisionLineColor);
for (int i = 0; i < mPasswordNumber - 1; i++) {
int startX = mBgSize + (i + 1) * mPasswordItemWidth + i * mDivisionLineSize;
int startY = mBgSize;
int endX = startX;
int endY = getHeight() - mBgSize;
canvas.drawLine(startX, startY, endX, endY, mPaint);
}
}
/**
* 绘制背景
*/
private void drawBg(Canvas canvas) {
RectF rect = new RectF(mBgSize, mBgSize, getWidth() - mBgSize, getHeight() - mBgSize);
// 给画笔设置大小
mPaint.setStrokeWidth(mBgSize);
// 设置背景的颜色
mPaint.setColor(mBgColor);
// 画空心
mPaint.setStyle(Paint.Style.STROKE);
// 绘制背景 drawRect , drawRoundRect ,
// 如果有圆角那么就绘制drawRoundRect,否则绘制drawRect
if (mBgCorner == 0) {
canvas.drawRect(rect, mPaint);
} else {
canvas.drawRoundRect(rect, mBgCorner, mBgCorner, mPaint);
}
}
/**
* 添加一个密码
*/
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 {
public void passwordFull(String password);
}
}
2.自定义键盘
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class CustomKeyboard extends LinearLayout implements View.OnClickListener {
public CustomKeyboard(Context context) {
this(context, null);
}
public CustomKeyboard(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomKeyboard(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 直接加载布局
inflate(context,R.layout.ui_custom_keyboard,this);
setItemClickListener(this);
}
/**
* 设置子View的ClickListener
*/
private void setItemClickListener(View view) {
if(view instanceof ViewGroup){
ViewGroup viewGroup = (ViewGroup) view;
int childCount = viewGroup.getChildCount();
for (int i=0;i<childCount;i++){
//不断的递归给里面所有的View设置OnClickListener
View childView = viewGroup.getChildAt(i);
setItemClickListener(childView);
}
}else{
view.setOnClickListener(this);
}
}
@Override
public void onClick(View v) {
if(v instanceof TextView){
// 点击的是数字
String number = ((TextView)v).getText().toString().trim();
if(mListener != null){
mListener.click(number);
}
}
if(v instanceof ImageView){
// 点击的是删除
if(mListener != null){
mListener.delete();
}
}
}
// 设置点击回掉监听
private CustomerKeyboardClickListener mListener;
public void setOnCustomerKeyboardClickListener(CustomerKeyboardClickListener listener){
this.mListener = listener;
}
/**
* 点击键盘的回调监听
*/
public interface CustomerKeyboardClickListener {
public void click(String number);
public void delete();
}
}
3.MainActivity
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.xinrui.dialog.CommonDialog;
public class MainActivity extends Activity implements CustomKeyboard.CustomerKeyboardClickListener, PsdEditText.PasswordFullListener, View.OnClickListener {
private CustomKeyboard mCustomKeyboard;
private PsdEditText mPsdEt;
private TextView mTiptextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* mCustomKeyboard = (CustomerKeyboard) findViewById(R.id.customer_keyboard);
mCustomKeyboard.setOnCustomerKeyboardClickListener(this);
mPsdEt = (PasswordEditText) findViewById(R.id.password_et);
mPsdEt.setEnabled(false);
mPsdEt.setOnPasswordFullListener(this);*/
mTiptextView = (TextView) findViewById(R.id.tip_txt);
mTiptextView.setOnClickListener(this);
}
@Override
public void click(String number) {
mPsdEt.addPassword(number);
}
@Override
public void delete() {
mPsdEt.deleteLastPassword();
}
@Override
public void passwordFull(String password) {
// 显示进度条去后台校验密码
Toast.makeText(this,"密码输入完毕->"+password,Toast.LENGTH_LONG).show();
}
@Override
public void onClick(View v) {
// 弹出dialog 从底部并且带动画
CommonDialog.Builder builder = new CommonDialog.Builder(this);
builder.setView(R.layout.customer_keyboard).fromBottom().fullWidth().create().show();
mPsdEt = builder.getView(R.id.password_edit_text);
mCustomKeyboard = builder.getView(R.id.custom_key_board);
mCustomKeyboard.setOnCustomerKeyboardClickListener(this);
mPsdEt.setEnabled(false);
mPsdEt.setOnPasswordFullListener(this);
}
}
3.customer_keyboard.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:background="#f7f7f7"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_height="wrap_content">
<ImageView
android:padding="10dp"
android:id="@+id/delete_dialog"
android:layout_width="wrap_content"
android:layout_centerVertical="true"
android:src="@mipmap/delete_black"
android:layout_height="wrap_content" />
<TextView
android:padding="15dp"
android:layout_width="wrap_content"
android:layout_centerInParent="true"
android:textSize="20sp"
android:textColor="#444a59"
android:text="输入密码"
android:layout_height="wrap_content" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.6dp"
android:background="#d1d2d6"
/>
<com.xinrui.PsdEditText
android:id="@+id/password_edit_text"
android:layout_width="match_parent"
android:layout_marginTop="23dp"
android:layout_marginRight="45dp"
android:background="@null"
android:padding="10dp"
app:bgCorner="3dp"
android:layout_marginLeft="45dp"
android:focusable="false"
android:focusableInTouchMode="false"
android:layout_height="wrap_content" />
<com.xinrui.CustomKeyboard
android:id="@+id/custom_key_board"
android:layout_marginTop="23dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
ui_custom_keyboard.xml
<?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: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_vertical"
android:src="@drawable/customer_password_keyboard_delete" />
</LinearLayout>
</LinearLayout>
CommonDialog
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ListView;
import com.xinrui.R;
public class CommonDialog extends Dialog {
private AlertController mAlert;
public CommonDialog(Context context, int theme) {
super(context, theme);
mAlert = new AlertController(getWindow(), this);
}
/**
* Look for a child view with the given id. If this view has the given id,
* return this view.
* <p>
* The id to search for.
*
* @return The view that has the given id in the hierarchy or null
*/
public <T extends View> T getView(int viewId) {
return mAlert.getView(viewId);
}
/**
* Set the resource id of the {@link Drawable} to be used in the title.
* <p>
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public void setIcon(int viewId, int resouceId) {
mAlert.setIcon(viewId, resouceId);
}
/**
* Set the {@link Drawable} to be used in the title.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public void setIcon(int viewId, Drawable drawable) {
mAlert.setIcon(viewId, drawable);
}
/**
* Creates a {@link AlertDialog} with the arguments supplied to this builder
* and {@link Dialog#show()}'s the com.hc.passwordedittext.dialog.
*/
public void setText(int viewId, CharSequence text) {
mAlert.setText(viewId, text);
}
/**
* Creates a {@link AlertDialog} with the arguments supplied to this builder
* and {@link Dialog#show()}'s the com.hc.passwordedittext.dialog.
*/
public void setOnClickListener(int viewId, View.OnClickListener listener) {
mAlert.setOnClickListener(viewId, listener);
}
public static class Builder {
private int mTheme;
private final AlertController.AlertParams P;
public Builder(Context context, int theme) {
this.mTheme = theme;
P = new AlertController.AlertParams(context);
}
public Builder(Context context) {
this(context, R.style.dialog);
}
public CommonDialog create() {
final CommonDialog dialog = new CommonDialog(P.mContext, mTheme);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
/**
* Returns a {@link Context} with the appropriate theme for dialogs
* created by this Builder. Applications should use this Context for
* obtaining LayoutInflaters for inflating views that will be used in
* the resulting dialogs, as it will cause views to be inflated with the
* correct theme.
*
* @return A Context for built Dialogs.
*/
public Context getContext() {
return P.mContext;
}
/**
* Set the resource id of the {@link Drawable} to be used in the title.
* <p>
*
* @return This Builder object to allow for chaining of calls to set
* methods
*/
public Builder setIcon(int viewId, int resouceId) {
P.setIcon(viewId, resouceId);
return this;
}
/**
* Set the {@link Drawable} to be used in the title.
*
* @return This Builder object to allow for chaining of calls to set
* methods
*/
public Builder setIcon(int viewId, Drawable icon) {
P.setIcon(viewId, icon);
return this;
}
/**
* Sets whether the com.hc.passwordedittext.dialog is cancelable or not. Default is true.
*
* @return This Builder object to allow for chaining of calls to set
* methods
*/
public Builder setCancelable(boolean cancelable) {
P.mCancelable = cancelable;
return this;
}
/**
* Sets the callback that will be called if the com.hc.passwordedittext.dialog is canceled.
*
* <p>
* Even in a cancelable com.hc.passwordedittext.dialog, the com.hc.passwordedittext.dialog may be dismissed for reasons
* other than being canceled or one of the supplied choices being
* selected. If you are interested in listening for all cases where the
* com.hc.passwordedittext.dialog is dismissed and not just when it is canceled, see
* {@link #setOnDismissListener(OnDismissListener)
* setOnDismissListener}.
* </p>
*
* @return This Builder object to allow for chaining of calls to set
* methods
* @see #setCancelable(boolean)
* @see #setOnDismissListener(OnDismissListener)
*/
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
P.mOnCancelListener = onCancelListener;
return this;
}
/**
* Sets the callback that will be called when the com.hc.passwordedittext.dialog is dismissed
* for any reason.
*
* @return This Builder object to allow for chaining of calls to set
* methods
*/
public Builder setOnDismissListener(OnDismissListener onDismissListener) {
P.mOnDismissListener = onDismissListener;
return this;
}
/**
* Sets the callback that will be called if a key is dispatched to the
* com.hc.passwordedittext.dialog.
*
* @return This Builder object to allow for chaining of calls to set
* methods
*/
public Builder setOnKeyListener(OnKeyListener onKeyListener) {
P.mOnKeyListener = onKeyListener;
return this;
}
/**
* Set a custom view resource to be the contents of the Dialog. The
* resource will be inflated, adding all top-level views to the screen.
*
* @param layoutResId Resource ID to be inflated.
* @return This Builder object to allow for chaining of calls to set
* methods
*/
public Builder setView(int layoutResId) {
P.mView = null;
P.mViewLayoutResId = layoutResId;
return this;
}
/**
* Set a custom view to be the contents of the Dialog. If the supplied
* view is an instance of a {@link ListView} the light background will
* be used.
*
* @param view The view to use as the contents of the Dialog.
* @return This Builder object to allow for chaining of calls to set
* methods
*/
public Builder setView(View view) {
P.mView = view;
P.mViewLayoutResId = 0;
return this;
}
/**
* Creates a {@link AlertDialog} with the arguments supplied to this
* builder and {@link Dialog#show()}'s the com.hc.passwordedittext.dialog.
*/
public Builder setText(int id, CharSequence text) {
P.setText(id, text);
return this;
}
/**
* Creates a {@link AlertDialog} with the arguments supplied to this
* builder and {@link Dialog#show()}'s the com.hc.passwordedittext.dialog.
*/
public Builder setOnClickListener(int id, View.OnClickListener listener) {
P.setOnClickListener(id, listener);
return this;
}
/**
* Dismiss this com.hc.passwordedittext.dialog, removing it from the screen. This method can be
* invoked safely from any thread. Note that you should not override
* this method to do cleanup when the com.hc.passwordedittext.dialog is dismissed, instead
* implement that in {@link #onStop}.
*/
public void dismiss() {
P.dismiss();
}
/**
* Look for a child view with the given id. If this view has the given
* id, return this view.
* <p>
* The id to search for.
*
* @return The view that has the given id in the hierarchy or null
*/
public <T extends View> T getView(int viewId) {
return P.getView(viewId);
}
/**
* Animation from the bottom of the pop-up to the middle all the time
*/
public Builder fromBottomToMiddle() {
P.fromBottomToMiddle();
return this;
}
/**
* Copy IOS pop-up effect
*/
public Builder fromBottom() {
P.fromBottom();
return this;
}
/**
* Special value for the height or width requested by a View.
* MATCH_PARENT means that the view wants to be as big as its parent,
* minus the parent's padding, if any. Introduced in API Level 8.
*/
public Builder fullWidth() {
P.setWidth(LayoutParams.MATCH_PARENT);
return this;
}
/**
* Loads a displacement of the default zoom animation
*/
public Builder loadAniamtion() {
P.loadAniamtion();
return this;
}
/**
* Information about how wide the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT , in API Level 8) or
* WRAP_CONTENT. or an exact size.
*/
public Builder setWidth(int width) {
P.setWidth(width);
return this;
}
/**
* Information about how tall the view wants to be. Can be one of the
* constants FILL_PARENT (replaced by MATCH_PARENT , in API Level 8) or
* WRAP_CONTENT. or an exact size.
*/
public Builder setHeight(int height) {
P.setHeight(height);
return this;
}
}
}