因项目要求,需要实现钱包支付功能。故需要自定义支付时的密码输入页面,主要涉及参考支付宝支付。经过短时间的折腾,小编算是弄出了大概样子,实际效果和实现大家可以参照着修改。当然,代码仍需要进一步完善才行。
这里小编自定义其宽度自适应高度从.xml形成,重写onDraw();方法,在这里面主要是实现的是密码输入框的显示,包括边框,分割线以及最主要的密码实心圆的绘制。当然我们需要监听编辑框的内容变化并通过调用invalidate();实时更新实心圆的数量和状态。其中,绘制的数量其实就是编辑框的文本长度,至于具体输入什么内容可以通过getText();获取就行了。
public class GridPwdEditText extends EditText {
// 边框大小 (分割线/3)
private float mBoardSize = 1.5f;
// 圆角大小
private float mRadius = 15;
// 可输入密码长度
public int mLength = 6;
// 密码●的半径
private float mCircleRadius = 10;
// 画笔
private Paint mPaint;
public GridPwdEditText(Context context) {
super(context);
init(context);
}
public GridPwdEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public GridPwdEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
InputFilter[] filters = {new InputFilter.LengthFilter(mLength)};
setFilters(filters);
setMaxLines(1);
setFocusable(false);
setFocusableInTouchMode(false);
setEnabled(false);// 设置不可编辑
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float width = getWidth() - mBoardSize;
float height = getHeight() - mBoardSize;
RectF rectf = new RectF();
rectf.left = mBoardSize;
rectf.top = mBoardSize;
rectf.right = width;
rectf.bottom = height;
canvas.drawColor(Color.WHITE);
// 绘制边框以及分割线
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mBoardSize / 3);
for (int i = 1, k = mLength; i < k; i++) {
float xPoint = width * i / k;
canvas.drawLine(xPoint, 0, xPoint, height, mPaint);
}
mPaint.setStrokeWidth(mBoardSize);
canvas.drawRoundRect(rectf, mRadius, mRadius, mPaint);
// 绘制密码圆点
mPaint.setStyle(Paint.Style.FILL);
for (int i = 1, k = getText().length(); i <= k; i++) {// k_已输入密码长度
// xPoint_分割平均点位置 - 最小间距(第一个符号/最后一个符号)- 符号本身大小/2
float xPoint = width * i / mLength - width / (mLength * 2) - mCircleRadius / 2 ;
float yPoint = height / 2;
canvas.drawCircle(xPoint, yPoint, mCircleRadius, mPaint);
}
}
/** * @param pwd 更新当前输入的密码 */
public void updatePwd(String pwd){
this.setText(pwd);
invalidate();// 更新密码显示●
}
}
除了密码显示框以外我们还得自定义一个数值键盘才行,小编看到有部分大手直接通过.xml中不对追加Button实现,而这里小编则是通过基础GridView来实现。该实现同样需要定义好每个Item项所代表的数值或者操作(比如说回退,这里回退按钮引用系统原声的icon,自行定义修改吧)。既然我们已经能拿到每个Item的点击了,那么我们需要将每次点击后的数值采集并组装成字符串,给到上面密码编辑框并更新密码显示。
public class GridPwdKeyBoard extends GridView {
private Adapter mAdapter;
private Context mContext;
private int mFontSize = 14;
private Integer[] keyNum = {1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -2};
// 当前输入的密码
public String mInputPwd = "";
// 最大输入长度
private int maxInput;
public GridPwdKeyBoard(Context context) {
super(context);
this.mContext = context;
initView();
}
public GridPwdKeyBoard(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initView();
}
public GridPwdKeyBoard(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initView();
}
/** * @param maxLength 最大输入长度 */
public void setMaxInput(int maxLength) {
this.maxInput = maxLength;
}
private void initView() {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
this.setLayoutParams(params);
this.setNumColumns(AUTO_FIT);
this.setStretchMode(STRETCH_COLUMN_WIDTH);
this.setBackgroundColor(Color.WHITE);
this.setNumColumns(3);
this.setHorizontalSpacing(3);
this.setVerticalSpacing(3);
this.setOnItemClickListener(onItemClick);
mAdapter = new Adapter(Arrays.asList(keyNum));
this.setAdapter(mAdapter);
}
public class Adapter extends BaseAdapter {
List<Integer> infos = new ArrayList<>();
public Adapter(List<Integer> infos) {
this.infos = infos;
}
@Override
public int getCount() {
return infos.size();
}
@Override
public Object getItem(int position) {
return infos.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView keyTv = new TextView(mContext);
keyTv.setTextSize(DisplayUtil.dip2px(mContext, mFontSize));
keyTv.setTextColor(Color.rgb(60, 60, 60));
keyTv.setGravity(Gravity.CENTER);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.MATCH_PARENT);
keyTv.setLayoutParams(params);
int numStr = infos.get(position);
if (numStr > -1) {// 普通数字
keyTv.setText(String.valueOf(numStr));
} else if (numStr < -1) {// 回退按钮
params = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
getHeight() / 4);
ImageView deleteImg = new ImageView(mContext);
deleteImg.setBackgroundColor(Color.argb(80, 227, 232, 238));
deleteImg.setLayoutParams(params);
deleteImg.setImageResource(android.R.drawable.ic_input_delete);
deleteImg.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
return deleteImg;
} else {
keyTv.setBackgroundColor(Color.argb(80, 227, 232, 238));
keyTv.setOnClickListener(null);
}
return keyTv;
}
}
AdapterView.OnItemClickListener onItemClick = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int numStr = keyNum[position];
if (mInputPwd.length() < maxInput && numStr > -1) {
mInputPwd += String.valueOf(numStr);
} else if (mInputPwd.length() > 0 && numStr < -1) {
// 点击回退按钮,替换最后一个字符为""
mInputPwd = mInputPwd.substring(0, mInputPwd.length() - 1);
}
if (mListener != null) {
mListener.keyCall(mInputPwd, mInputPwd.length() == maxInput);
}
}
};
public void setOnKeyBoardListener(OnKeyBoardListener listener) {
this.mListener = listener;
}
private OnKeyBoardListener mListener;
public interface OnKeyBoardListener {
/** * @param inputStr 当前用户输入密码 * @param isOk true_输入密码已达到指定长度 */
void keyCall(String inputStr, boolean isOk);
}
}
上面我们已经把编辑框和键盘都设计好了,接下来就该根据个人项目需求组装起来了。这里小编仅根据支付宝的显示样式实现。引用的时候记得更改自定义视图的路径。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#F7F7F7" android:orientation="vertical" android:layout_alignParentBottom="true">
<RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content">
<ImageButton android:id="@+id/pwd_ivb_cancle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@null" android:padding="12dp" android:src="@mipmap/ic_common_done_cancle" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="输入密码" android:textColor="#222222" android:textSize="16sp" />
</RelativeLayout>
<View android:layout_width="match_parent" android:layout_height="0.5dp" android:background="#C8C8C8" />
<自己的路径.GridPwdEditText android:id="@+id/pwd_view_edt" android:layout_width="match_parent" android:layout_height="50dp" android:layout_margin="12dp" />
<TextView android:id="@+id/pwd_tv_forget" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:layout_marginEnd="12dp" android:layout_marginRight="12dp" android:text="忘记密码?" android:textColor="#3F51B5" />
<自己的路径.GridPwdKeyBoard android:id="@+id/pwd_gv_keyboard" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dp"/>
</LinearLayout>
</RelativeLayout>
好吧,实际上是继承了PopupWindow实现的对话框。源码带中小编也另外加上了密码输入状态的接口回调监听,为的就是能方便我们取得用户输入的密码或者是否点击了忘记密码的操作。如果有其他见解或者需求请自行完善。
public class GridPwdInputDialog extends PopupWindow {
private ImageButton mCancleIvb;
private GridPwdEditText mPwdEdt;
private TextView mForgetTv;
private GridPwdKeyBoard mKeyBoardGv;
private Context mContext;
public GridPwdInputDialog(Context context) {
super(context);
this.mContext = context;
initView();
}
private void initView() {
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View conentView = inflater.inflate(R.layout.dialog_pwd_input, null);
mCancleIvb = (ImageButton) conentView.findViewById(R.id.pwd_ivb_cancle);
mPwdEdt = (GridPwdEditText) conentView.findViewById(R.id.pwd_view_edt);
mForgetTv = (TextView) conentView.findViewById(R.id.pwd_tv_forget);
mKeyBoardGv = (GridPwdKeyBoard) conentView.findViewById(R.id.pwd_gv_keyboard);
mKeyBoardGv.setOnKeyBoardListener(onKeyBoard);
mKeyBoardGv.setMaxInput(mPwdEdt.mLength);
mCancleIvb.setOnClickListener(onClick);
mForgetTv.setOnClickListener(onClick);
// 设置SelectPicPopupWindow的View
this.setContentView(conentView);
// 设置SelectPicPopupWindow弹出窗体的宽
this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
// 设置SelectPicPopupWindow弹出窗体的高
this.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
// 设置SelectPicPopupWindow弹出窗体可点击
this.setFocusable(false);
this.setOutsideTouchable(false);
// 刷新状态
this.update();
// 实例化一个ColorDrawable颜色为半透明
ColorDrawable dw = new ColorDrawable(Color.argb(100, 0, 0, 0));
// 点back键和其他地方使其消失,设置了这个才能触发OnDismisslistener ,设置其他控件变化等操作
this.setBackgroundDrawable(dw);
// 设置SelectPicPopupWindow弹出窗体动画效果
this.setAnimationStyle(android.R.style.Animation_Dialog);
this.setOnDismissListener(onDismiss);
}
GridPwdKeyBoard.OnKeyBoardListener onKeyBoard = new GridPwdKeyBoard.OnKeyBoardListener() {
@Override
public void keyCall(String inputStr, boolean isOk) {
mPwdEdt.updatePwd(inputStr);
if (isOk && mListener != null) {
mListener.inPutEnd(mKeyBoardGv.mInputPwd, 1);
}
}
};
View.OnClickListener onClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.pwd_ivb_cancle:
dismiss();
break;
case R.id.pwd_tv_forget:
if (mListener != null) {
mListener.inPutEnd(mKeyBoardGv.mInputPwd, 0);
}
break;
}
}
};
OnDismissListener onDismiss = new OnDismissListener() {
@Override
public void onDismiss() {
mKeyBoardGv.mInputPwd = "";
mPwdEdt.updatePwd("");
}
};
public void setOnInPutListener(OnInPutdListener listener) {
this.mListener = listener;
}
private OnInPutdListener mListener;
public interface OnInPutdListener {
/** * @param inputStr 当前用户输入密码 * @param code 0_输入密码状态,1_忘记密码 */
void inPutEnd(String inputStr, int code);
}
}
最后仅贴一下dialog的引用和设置监听。相信该demo还有很多不足的地方,毕竟小编很少接触自定义控件(组件)的实现,所以不管是理论上还是实现上都还有很多需要注意和完善的地方。如果对该自定义demo实现有见解请及时艾特小编吧。
private void showDialog(){
GridPwdInputDialog mDialog = new GridPwdInputDialog(this);
mDialog.setOnInPutListener(onInPut);
mDialog.showAtLocation(view, Gravity.BOTTOM, 0, 0);
}
GridPwdInputDialog.OnInPutdListener onInPut = new GridPwdInputDialog.OnInPutdListener() {
@Override
public void inPutEnd(String inputStr, int code) {
switch (code) {
case 0:break;// 忘记密码
case 1:break;// 完成密码输入
}
}
};