25 | * 作者姓名 修改时间 版本号 描述
26 | */
27 | public class PasswordLayoutView extends LinearLayout {
28 |
29 |
30 | private TextView[] tvList; //用数组保存6个TextView,为什么用数组?
31 |
32 | private ImageView[] imgList; //用数组保存6个TextView,为什么用数组?
33 |
34 |
35 | private OnPasswordInputFinish onPasswordInputFinish;
36 |
37 | private PasswordLayoutListener mPasswordLayoutListener;
38 |
39 |
40 | public PasswordLayoutView(Context context) {
41 | this(context, null);
42 | }
43 |
44 | public PasswordLayoutView(Context context, @Nullable AttributeSet attrs) {
45 | this(context, attrs, 0);
46 | }
47 |
48 | public PasswordLayoutView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
49 | super(context, attrs, defStyleAttr);
50 |
51 | LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
52 | View view = layoutInflater.inflate(R.layout.layout_password_view, this, true);
53 | setOrientation(HORIZONTAL);
54 | //@drawable/pwd_input_area_bg
55 | setBackgroundDrawable(getResources().getDrawable(R.drawable.pwd_input_area_bg));
56 | initView(view);
57 | }
58 |
59 |
60 | private void initView(View view) {
61 |
62 |
63 | tvList = new TextView[6];
64 |
65 | imgList = new ImageView[6];
66 |
67 | tvList[0] = (TextView) view.findViewById(R.id.tv_pass1);
68 | tvList[1] = (TextView) view.findViewById(R.id.tv_pass2);
69 | tvList[2] = (TextView) view.findViewById(R.id.tv_pass3);
70 | tvList[3] = (TextView) view.findViewById(R.id.tv_pass4);
71 | tvList[4] = (TextView) view.findViewById(R.id.tv_pass5);
72 | tvList[5] = (TextView) view.findViewById(R.id.tv_pass6);
73 |
74 |
75 | imgList[0] = (ImageView) view.findViewById(R.id.img_pass1);
76 | imgList[1] = (ImageView) view.findViewById(R.id.img_pass2);
77 | imgList[2] = (ImageView) view.findViewById(R.id.img_pass3);
78 | imgList[3] = (ImageView) view.findViewById(R.id.img_pass4);
79 | imgList[4] = (ImageView) view.findViewById(R.id.img_pass5);
80 | imgList[5] = (ImageView) view.findViewById(R.id.img_pass6);
81 |
82 | // for (int i = 0; i < 6; i++) {
83 | // imgList[i].setOnClickListener(new OnClickListener() {
84 | // @Override
85 | // public void onClick(View view) {
86 | // if (mPasswordLayoutListener != null) {
87 | // mPasswordLayoutListener.onclick();
88 | // }
89 | // }
90 | // });
91 | // tvList[i].setOnClickListener(new OnClickListener() {
92 | // @Override
93 | // public void onClick(View view) {
94 | // if (mPasswordLayoutListener != null) {
95 | // mPasswordLayoutListener.onclick();
96 | // }
97 | // }
98 | // });
99 | // }
100 | }
101 |
102 |
103 | @Override
104 | public boolean onInterceptTouchEvent(MotionEvent ev) {
105 | return true;
106 | }
107 |
108 | public void show(int currentIndex, String text) {
109 | tvList[currentIndex].setText(text);
110 |
111 | tvList[currentIndex].setVisibility(View.INVISIBLE);
112 | imgList[currentIndex].setVisibility(View.VISIBLE);
113 | }
114 |
115 | public void del(int currentIndex) {
116 |
117 | tvList[currentIndex].setText("");
118 |
119 | tvList[currentIndex].setVisibility(View.VISIBLE);
120 | imgList[currentIndex].setVisibility(View.INVISIBLE);
121 |
122 | }
123 |
124 | public void setOnPasswordInputFinish(OnPasswordInputFinish onPasswordInputFinish) {
125 | this.onPasswordInputFinish = onPasswordInputFinish;
126 | }
127 |
128 |
129 | public void setClickListener(PasswordLayoutListener clickListener) {
130 | this.mPasswordLayoutListener = clickListener;
131 | //父view拦截子view的点击事件
132 | setOnClickListener(new OnClickListener() {
133 | @Override
134 | public void onClick(View v) {
135 | if (mPasswordLayoutListener != null) {
136 | mPasswordLayoutListener.onclick();
137 | }
138 | }
139 | });
140 | }
141 |
142 |
143 | /**
144 | * 在第五个按钮上增加回调
145 | */
146 | public void addFinishListener() {
147 | tvList[5].addTextChangedListener(new TextWatcher() {
148 | @Override
149 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
150 |
151 | }
152 |
153 | @Override
154 | public void onTextChanged(CharSequence s, int start, int before, int count) {
155 |
156 | }
157 |
158 | @Override
159 | public void afterTextChanged(Editable s) {
160 |
161 | if (s.toString().length() == 1) {
162 |
163 | String strPassword = ""; //每次触发都要先将strPassword置空,再重新获取,避免由于输入删除再输入造成混乱
164 |
165 | for (int i = 0; i < 6; i++) {
166 | strPassword += tvList[i].getText().toString().trim();
167 | }
168 | if (onPasswordInputFinish != null) {
169 | onPasswordInputFinish.inputFinish(strPassword); //接口中要实现的方法,完成密码输入完成后的响应逻辑
170 | }
171 | } else {
172 | if (onPasswordInputFinish != null) {
173 | onPasswordInputFinish.inputCancel();
174 | }
175 | }
176 | }
177 | });
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/VerificationCodeView.java:
--------------------------------------------------------------------------------
1 | package com.moziqi.pwd.widget;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Color;
7 | import android.graphics.drawable.Drawable;
8 | import android.text.Editable;
9 | import android.text.InputFilter;
10 | import android.text.InputType;
11 | import android.text.TextUtils;
12 | import android.text.TextWatcher;
13 | import android.util.AttributeSet;
14 | import android.view.Gravity;
15 | import android.view.KeyEvent;
16 | import android.view.View;
17 | import android.widget.LinearLayout;
18 | import android.widget.TextView;
19 |
20 | import com.moziqi.pwd.R;
21 |
22 | import java.lang.reflect.Field;
23 |
24 | /**
25 | * 参考这个调整
26 | * https://github.com/jiaoyaning/VerificationCodeView
27 | *
28 | * jiaoyaning
29 | * com.b2cf.nonghe.widget
30 | * VerificationCodeView
31 | * 2017/10/31
32 | */
33 |
34 | public class VerificationCodeView extends LinearLayout implements TextWatcher, View.OnKeyListener, View.OnFocusChangeListener {
35 |
36 | private Context mContext;
37 | private long endTime = 0;
38 | private OnCodeFinishListener onCodeFinishListener;
39 |
40 | /**
41 | * 输入框数量
42 | */
43 | private int mEtNumber;
44 |
45 | /**
46 | * 输入框类型
47 | */
48 | private VCInputType mEtInputType;
49 | /**
50 | * 输入框的宽度
51 | */
52 | private int mEtWidth;
53 |
54 | /**
55 | * 文字颜色
56 | */
57 | private int mEtTextColor;
58 |
59 | /**
60 | * 文字大小
61 | */
62 | private float mEtTextSize;
63 |
64 | /**
65 | * 输入框背景
66 | */
67 | private int mEtTextBg;
68 |
69 | private int mCursorDrawable;
70 |
71 | public OnCodeFinishListener getOnCodeFinishListener() {
72 | return onCodeFinishListener;
73 | }
74 |
75 | public void setOnCodeFinishListener(OnCodeFinishListener onCodeFinishListener) {
76 | this.onCodeFinishListener = onCodeFinishListener;
77 | }
78 |
79 | public int getmEtNumber() {
80 | return mEtNumber;
81 | }
82 |
83 | public void setmEtNumber(int mEtNumber) {
84 | this.mEtNumber = mEtNumber;
85 | }
86 |
87 | public VCInputType getmEtInputType() {
88 | return mEtInputType;
89 | }
90 |
91 | public void setmEtInputType(VCInputType mEtInputType) {
92 | this.mEtInputType = mEtInputType;
93 | }
94 |
95 | public int getmEtWidth() {
96 | return mEtWidth;
97 | }
98 |
99 | public void setmEtWidth(int mEtWidth) {
100 | this.mEtWidth = mEtWidth;
101 | }
102 |
103 | public int getmEtTextColor() {
104 | return mEtTextColor;
105 | }
106 |
107 | public void setmEtTextColor(int mEtTextColor) {
108 | this.mEtTextColor = mEtTextColor;
109 | }
110 |
111 | public float getmEtTextSize() {
112 | return mEtTextSize;
113 | }
114 |
115 | public void setmEtTextSize(float mEtTextSize) {
116 | this.mEtTextSize = mEtTextSize;
117 | }
118 |
119 | public int getmEtTextBg() {
120 | return mEtTextBg;
121 | }
122 |
123 | public void setmEtTextBg(int mEtTextBg) {
124 | this.mEtTextBg = mEtTextBg;
125 | }
126 |
127 | public int getmCursorDrawable() {
128 | return mCursorDrawable;
129 | }
130 |
131 | public void setmCursorDrawable(int mCursorDrawable) {
132 | this.mCursorDrawable = mCursorDrawable;
133 | }
134 |
135 | public enum VCInputType {
136 | NUMBER,
137 | NUMBERPASSWORD,
138 | TEXT,
139 | TEXTPASSWORD,
140 | }
141 |
142 | public VerificationCodeView(Context context, AttributeSet attrs) {
143 | super(context, attrs);
144 | this.mContext = context;
145 | @SuppressLint({"Recycle", "CustomViewStyleable"})
146 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.vericationCodeView);
147 | mEtNumber = typedArray.getInteger(R.styleable.vericationCodeView_vcv_et_number, 6);
148 | int inputType = typedArray.getInt(R.styleable.vericationCodeView_vcv_et_inputType, VCInputType.NUMBER.ordinal());
149 | mEtInputType = VCInputType.values()[inputType];
150 | mEtWidth = typedArray.getDimensionPixelSize(R.styleable.vericationCodeView_vcv_et_width, 120);
151 | mEtTextColor = typedArray.getColor(R.styleable.vericationCodeView_vcv_et_text_color, Color.BLACK);
152 | mEtTextSize = typedArray.getDimensionPixelSize(R.styleable.vericationCodeView_vcv_et_text_size, 16);
153 | mEtTextBg = typedArray.getResourceId(R.styleable.vericationCodeView_vcv_et_bg, R.drawable.pwd_et_login_code);
154 | mCursorDrawable = typedArray.getResourceId(R.styleable.vericationCodeView_vcv_et_cursor, R.drawable.pwd_et_cursor);
155 |
156 | //释放资源
157 | typedArray.recycle();
158 | initView();
159 | }
160 |
161 | @SuppressLint("ResourceAsColor")
162 | private void initView() {
163 | for (int i = 0; i < mEtNumber; i++) {
164 | ZanyEditText editText = new ZanyEditText(mContext);
165 | initEditText(editText, i);
166 | addView(editText);
167 | if (i == 0) { //设置第一个editText获取焦点
168 | editText.setFocusable(true);
169 | }
170 | }
171 | }
172 |
173 | private void initEditText(ZanyEditText editText, int i) {
174 | int childHPadding = 14;
175 | int childVPadding = 14;
176 |
177 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT);
178 | layoutParams.weight = 1;
179 | layoutParams.bottomMargin = childVPadding;
180 | layoutParams.topMargin = childVPadding;
181 | layoutParams.leftMargin = childHPadding;
182 | layoutParams.rightMargin = childHPadding;
183 | layoutParams.gravity = Gravity.CENTER;
184 | editText.setLayoutParams(layoutParams);
185 | editText.setGravity(Gravity.CENTER);
186 | editText.setId(i);
187 | editText.setCursorVisible(true);
188 | editText.setMaxEms(1);
189 | editText.setTextColor(mEtTextColor);
190 | editText.setTextSize(mEtTextSize);
191 | editText.setMaxLines(1);
192 | editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)});
193 | switch (mEtInputType) {
194 | case NUMBER:
195 | editText.setInputType(InputType.TYPE_CLASS_NUMBER);
196 | break;
197 | case NUMBERPASSWORD:
198 | editText.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
199 | break;
200 | case TEXT:
201 | editText.setInputType(InputType.TYPE_CLASS_TEXT);
202 | break;
203 | case TEXTPASSWORD:
204 | editText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
205 | break;
206 | default:
207 | editText.setInputType(InputType.TYPE_CLASS_NUMBER);
208 | }
209 | editText.setPadding(0, 0, 0, 0);
210 | editText.setOnKeyListener(this);
211 | //editText.setBackgroundResource(mEtTextBg);
212 | editText.setBackgroundDrawable(null);
213 | Drawable drawable = getResources().getDrawable(mEtTextBg);
214 | drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
215 | editText.setCompoundDrawables(null, null, null, drawable);
216 |
217 | //修改光标的颜色(反射)
218 | try {
219 | Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
220 | f.setAccessible(true);
221 | f.set(editText, mCursorDrawable);
222 | } catch (Exception ignored) {
223 | }
224 |
225 | if (i == mEtNumber - 1) {
226 | editText.addTextChangedListener(new TextWatcher() {
227 | @Override
228 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
229 |
230 | }
231 |
232 | @Override
233 | public void onTextChanged(CharSequence s, int start, int before, int count) {
234 |
235 | }
236 |
237 | @Override
238 | public void afterTextChanged(Editable s) {
239 | if (s.length() != 0) {
240 | focus();
241 | }
242 | //如果最后一个输入框有字符,则返回结果
243 | if (!TextUtils.isEmpty(s.toString().trim())) {
244 | getResult();
245 | }
246 | }
247 | });
248 | editText.setOnFocusChangeListener(new OnFocusChangeListener() {
249 | @Override
250 | public void onFocusChange(View v, boolean hasFocus) {
251 | if (hasFocus) {
252 | focus();
253 | }
254 | }
255 | });
256 | } else {
257 | editText.setOnFocusChangeListener(this);
258 | editText.addTextChangedListener(this);
259 | }
260 | editText.setOnKeyListener(this);
261 | }
262 |
263 |
264 | @Override
265 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
266 |
267 | }
268 |
269 | @Override
270 | public void onTextChanged(CharSequence s, int start, int before, int count) {
271 |
272 | }
273 |
274 | @Override
275 | public void afterTextChanged(Editable s) {
276 | if (s.length() != 0) {
277 | focus();
278 | }
279 | }
280 |
281 | @Override
282 | public boolean onKey(View v, int keyCode, KeyEvent event) {
283 | if (keyCode == KeyEvent.KEYCODE_DEL
284 | && event.getAction() == KeyEvent.ACTION_DOWN) {
285 | backFocus();
286 | }
287 | return false;
288 | }
289 |
290 | @Override
291 | public void setEnabled(boolean enabled) {
292 | int childCount = getChildCount();
293 | for (int i = 0; i < childCount; i++) {
294 | View child = getChildAt(i);
295 | child.setEnabled(enabled);
296 | }
297 | }
298 |
299 | /**
300 | * 获取焦点
301 | */
302 | private void focus() {
303 | int count = getChildCount();
304 | ZanyEditText editText;
305 | //利用for循环找出还最前面那个还没被输入字符的EditText,并把焦点移交给它。
306 | for (int i = 0; i < count; i++) {
307 | editText = (ZanyEditText) getChildAt(i);
308 | if (editText.getText().length() < 1) {
309 | editText.setCursorVisible(true);
310 | editText.requestFocus();
311 | return;
312 | } else {
313 | editText.setCursorVisible(false);
314 | }
315 | }
316 | //强行最后一个一直拿焦点,不然删除时候出问题
317 | ZanyEditText lastEditText = (ZanyEditText) getChildAt(mEtNumber - 1);
318 | if (!TextUtils.isEmpty(lastEditText.getText().toString())) {
319 | lastEditText.requestFocus();
320 | }
321 | }
322 |
323 | private void backFocus() {
324 | //博主手机不好,经常点一次却触发两次`onKey`事件,就设置了一个防止多点击,间隔100毫秒。
325 | //long startTime = System.currentTimeMillis();
326 | ZanyEditText editText;
327 | //循环检测有字符的`editText`,把其置空,并获取焦点。
328 | for (int i = mEtNumber - 1; i >= 0; i--) {
329 | editText = (ZanyEditText) getChildAt(i);
330 | if (editText.getText().length() >= 1) {// && startTime - endTime > 100
331 | editText.setText("");
332 | editText.setCursorVisible(true);
333 | editText.requestFocus();
334 | //endTime = startTime;
335 | return;
336 | }
337 | }
338 | }
339 |
340 | private void getResult() {
341 | StringBuffer stringBuffer = new StringBuffer();
342 | ZanyEditText editText;
343 | for (int i = 0; i < mEtNumber; i++) {
344 | editText = (ZanyEditText) getChildAt(i);
345 | stringBuffer.append(editText.getText());
346 | }
347 | if (onCodeFinishListener != null) {
348 | onCodeFinishListener.onComplete(stringBuffer.toString());
349 | }
350 | }
351 |
352 | public void clearText() {
353 | ZanyEditText editText;
354 | for (int i = 0; i < mEtNumber; i++) {
355 | editText = (ZanyEditText) getChildAt(i);
356 | editText.setText("");
357 | }
358 | focus();
359 | }
360 |
361 | @Override
362 | public void onFocusChange(View v, boolean hasFocus) {
363 | if (hasFocus) {
364 | focus();
365 | }
366 | }
367 |
368 | public interface OnCodeFinishListener {
369 | void onComplete(String content);
370 | }
371 | }
--------------------------------------------------------------------------------
/pswkeyboardlibrary/src/main/java/com/moziqi/pwd/widget/VirtualKeyboardView.java:
--------------------------------------------------------------------------------
1 | package com.moziqi.pwd.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.util.AttributeSet;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.widget.GridView;
10 | import android.widget.RelativeLayout;
11 |
12 | import com.moziqi.pwd.R;
13 | import com.moziqi.pwd.adapter.KeyBoardAdapter;
14 |
15 | import java.util.ArrayList;
16 | import java.util.HashMap;
17 | import java.util.Map;
18 | import java.util.Random;
19 |
20 | /**
21 | * 虚拟键盘
22 | */
23 | public class VirtualKeyboardView extends RelativeLayout {
24 |
25 |
26 | Context context;
27 |
28 | //因为就6个输入框不会变了,用数组内存申请固定空间,比List省空间(自己认为)
29 | private GridView gridView; //用GrideView布局键盘,其实并不是真正的键盘,只是模拟键盘的功能
30 |
31 | private ArrayList