http://blog.csdn.net/ruancoder/article/details/52289833
GridPasswordView是一个密码输入视图,类似于微信或支付宝的支付密码视图。可以设置文字颜色和大小、分割线颜色、密码的长度。
项目地址:
https://github.com/Jungerr/GridPasswordView
其中包含项目源码和示例代码。
运行效果图:
一、项目使用
(1).在工程的build.gradle文件中添加项目引用。
- dependencies {
- compile 'com.jungly:gridPasswordView:0.3'
- }
(2).在布局中添加GridPasswordView。
- <com.jungly.gridpasswordview.GridPasswordView
- android:id="@+id/gridpassword"
- android:layout_width="match_parent"
- android:layout_height="50dp" />
(3).添加Java代码。
- GridPasswordView gridPasswordView = findViewById(R.id.gridpassword);
- gridPasswordView.setOnPasswordChangedListener(new GridPasswordView.OnPasswordChangedListener() {
- @Override
- public void onTextChanged(String psw) {
-
- }
-
- @Override
- public void onInputFinish(String psw) {
-
- }
- });
二、源码分析
实现原理:
整个网格密码输入框最外层是一个水平方向的LinearLayout,内部包括显示密码的View和垂直分隔线View。而多个显示密码的View中,第一个位置放置的是EditText,其余均为TextView。如图所示。
当LinearLayout被点击时,让EditText获取焦点并弹出软键盘。定义一个String类型数组保存已输入的密码,当监听到EditText有新的字符输入时,截取下来保存到数组中,并保持EditText中始终只存在一个字符。每当监听到EditText的退格键时,删除String数组的最后一条数据。最后遍历String数组得到的就是用户输入的密码。
代码核心部分包括视图的添加、字符输入的监听和退格键的监听。
(1).初始化视图
在构造方法中,完成View的初始化。包括强制设置方向为水平,添加一个EditText和多个TextView以及之间的分割线。由于EditText和多个TextView的layout_width=0且layout_weight=1,所以会均分宽度。
- public class GridPasswordView extends LinearLayout {
- private String[] mPasswordArr;
- private TextView[] mViewArr;
-
- public GridPasswordView(Context context) {
- super(context);
- mPasswordArr = new String[mPasswordLength];
- mViewArr = new TextView[mPasswordLength];
- initViews(context);
- }
-
- private void initViews(Context context) {
- super.setBackgroundDrawable(mOuterLineDrawable);
- setShowDividers(SHOW_DIVIDER_NONE);
-
- setOrientation(HORIZONTAL);
-
- mTransformationMethod = new CustomPasswordTransformationMethod(mPasswordTransformation);
- inflaterViews(context);
- }
-
-
- private void inflaterViews(Context context) {
-
- LayoutInflater inflater = LayoutInflater.from(context);
- inflater.inflate(R.layout.gridpasswordview, this);
-
- mInputView = (ImeDelBugFixedEditText) findViewById(R.id.inputView);
- mInputView.setMaxEms(mPasswordLength);
-
- mInputView.addTextChangedListener(textWatcher);
-
- mInputView.setDelKeyEventListener(onDelKeyEventListener);
- setCustomAttr(mInputView);
-
-
- mViewArr[0] = mInputView;
-
-
- int index = 1;
- while (index < mPasswordLength) {
-
- View dividerView = inflater.inflate(R.layout.divider, null);
- LayoutParams dividerParams = new LayoutParams(mLineWidth, LayoutParams.MATCH_PARENT);
- dividerView.setBackgroundDrawable(mLineDrawable);
- addView(dividerView, dividerParams);
-
-
- TextView textView = (TextView) inflater.inflate(R.layout.textview, null);
- setCustomAttr(textView);
- LayoutParams textViewParams = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1f);
- addView(textView, textViewParams);
-
- mViewArr[index] = textView;
- index++;
- }
-
-
- setOnClickListener(mOnClickListener);
- }
- }
(2).弹出软键盘
当LinearLayout被点击时,触发mOnClickListener。使EditText强制获取焦点,弹出键盘。
- private OnClickListener mOnClickListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- forceInputViewGetFocus();
- }
- };
-
-
- private void forceInputViewGetFocus() {
- mInputView.setFocusable(true);
- mInputView.setFocusableInTouchMode(true);
- mInputView.requestFocus();
- InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(mInputView, InputMethodManager.SHOW_IMPLICIT);
- }
(3).监听字符输入
使用TextWatcher监听EditText的字符输入。关键点在于使用数组保存每次输入的密码,和维持EditText内始终只显示首个密码字符。
- private TextWatcher textWatcher = new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- if (s == null) {
- return;
- }
-
- String newStr = s.toString();
- if (newStr.length() == 1) {
- mPasswordArr[0] = newStr;
- notifyTextChanged();
- } else if (newStr.length() == 2) {
-
- String newNum = newStr.substring(1);
- for (int i = 0; i < mPasswordArr.length; i++) {
- if (mPasswordArr[i] == null) {
-
- mPasswordArr[i] = newNum;
-
- mViewArr[i].setText(newNum);
-
- notifyTextChanged();
- break;
- }
- }
-
-
- mInputView.removeTextChangedListener(this);
- mInputView.setText(mPasswordArr[0]);
- if (mInputView.getText().length() >= 1) {
- mInputView.setSelection(1);
- }
- mInputView.addTextChangedListener(this);
- }
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- }
- };
(4).监听退格键
当监听到EditText内触发退格键时,删除mPasswordArr数组内最后一个字符。
- private ImeDelBugFixedEditText.OnDelKeyEventListener onDelKeyEventListener = new ImeDelBugFixedEditText
- .OnDelKeyEventListener() {
- @Override
- public void onDeleteClick() {
- for (int i = mPasswordArr.length - 1; i >= 0; i--) {
-
- if (mPasswordArr[i] != null) {
- mPasswordArr[i] = null;
- mViewArr[i].setText(null);
- notifyTextChanged();
- break;
- } else {
- mViewArr[i].setText(null);
- }
- }
- }
- };
关键在于EditText对退格键的监听。在有的软键盘中,OnKeyListener无法监听到键盘的退格键KeyEvent.KEYCODE_DEL事件,在stackoverflow中已经有人给出了解决方法:
http://stackoverflow.com/questions/4886858/android-edittext-deletebackspace-key-event
EditText被重写后的核心代码:
- public class ImeDelBugFixedEditText extends EditText {
- private OnDelKeyEventListener delKeyEventListener;
-
-
-
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- return new ZanyInputConnection(super.onCreateInputConnection(outAttrs), true);
- }
-
- private class ZanyInputConnection extends InputConnectionWrapper {
- public ZanyInputConnection(InputConnection target, boolean mutable) {
- super(target, mutable);
- }
-
-
- @Override
- public boolean sendKeyEvent(KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
- if (delKeyEventListener != null) {
- delKeyEventListener.onDeleteClick();
- return true;
- }
- }
- return super.sendKeyEvent(event);
- }
-
-
-
- @Override
- public boolean deleteSurroundingText(int beforeLength, int afterLength) {
- if (beforeLength == 1 && afterLength == 0) {
- return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) && sendKeyEvent(new KeyEvent
- (KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
- }
- return super.deleteSurroundingText(beforeLength, afterLength);
- }
- }
-
- public void setDelKeyEventListener(OnDelKeyEventListener delKeyEventListener) {
- this.delKeyEventListener = delKeyEventListener;
- }
-
- public interface OnDelKeyEventListener {
- void onDeleteClick();
- }
- }