开源项目GridPasswordView使用和源码分析

http://blog.csdn.net/ruancoder/article/details/52289833

GridPasswordView是一个密码输入视图,类似于微信或支付宝的支付密码视图。可以设置文字颜色和大小、分割线颜色、密码的长度。

项目地址:
https://github.com/Jungerr/GridPasswordView
其中包含项目源码和示例代码。

运行效果图:



一、项目使用
(1).在工程的build.gradle文件中添加项目引用。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. dependencies {  
  2.     compile 'com.jungly:gridPasswordView:0.3'  
  3. }  
(2).在布局中添加GridPasswordView。
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <com.jungly.gridpasswordview.GridPasswordView  
  2.     android:id="@+id/gridpassword"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="50dp" />  
(3).添加Java代码。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. GridPasswordView gridPasswordView = findViewById(R.id.gridpassword);  
  2. gridPasswordView.setOnPasswordChangedListener(new GridPasswordView.OnPasswordChangedListener() {  
  3.     @Override  
  4.     public void onTextChanged(String psw) {  
  5.         // add your code here  
  6.     }  
  7.   
  8.     @Override  
  9.     public void onInputFinish(String psw) {  
  10.         // add your code here  
  11.     }  
  12. });  

二、源码分析
实现原理:
整个网格密码输入框最外层是一个水平方向的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,所以会均分宽度。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class GridPasswordView extends LinearLayout {  
  2.     private String[] mPasswordArr;  
  3.     private TextView[] mViewArr;  
  4.   
  5.     public GridPasswordView(Context context) {  
  6.         super(context);  
  7.     mPasswordArr = new String[mPasswordLength];  
  8.         mViewArr = new TextView[mPasswordLength];  
  9.         initViews(context);  
  10.     }  
  11.   
  12.     private void initViews(Context context) {  
  13.         super.setBackgroundDrawable(mOuterLineDrawable);  
  14.         setShowDividers(SHOW_DIVIDER_NONE);  
  15.         // 设置方向为水平  
  16.         setOrientation(HORIZONTAL);  
  17.   
  18.         mTransformationMethod = new CustomPasswordTransformationMethod(mPasswordTransformation);  
  19.         inflaterViews(context);  
  20.     }  
  21.   
  22.     // 添加视图操作  
  23.     private void inflaterViews(Context context) {  
  24.         // 布局gridpasswordview中只有一个EditText,添加到当前视图  
  25.         LayoutInflater inflater = LayoutInflater.from(context);  
  26.         inflater.inflate(R.layout.gridpasswordview, this);  
  27.   
  28.         mInputView = (ImeDelBugFixedEditText) findViewById(R.id.inputView);  
  29.         mInputView.setMaxEms(mPasswordLength);  
  30.     // 添加监听  
  31.         mInputView.addTextChangedListener(textWatcher);  
  32.     // 添加监听  
  33.         mInputView.setDelKeyEventListener(onDelKeyEventListener);  
  34.         setCustomAttr(mInputView);  
  35.   
  36.         // 索引0是EditText  
  37.         mViewArr[0] = mInputView;  
  38.   
  39.         // 索引1~mPasswordLength是TextView  
  40.         int index = 1;  
  41.         while (index < mPasswordLength) {  
  42.             // 调用addView()添加分割线  
  43.             View dividerView = inflater.inflate(R.layout.divider, null);  
  44.             LayoutParams dividerParams = new LayoutParams(mLineWidth, LayoutParams.MATCH_PARENT);  
  45.             dividerView.setBackgroundDrawable(mLineDrawable);  
  46.             addView(dividerView, dividerParams);  
  47.   
  48.             // 调用addView()添加文本  
  49.             TextView textView = (TextView) inflater.inflate(R.layout.textview, null);  
  50.             setCustomAttr(textView);  
  51.             LayoutParams textViewParams = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1f);  
  52.             addView(textView, textViewParams);  
  53.   
  54.             mViewArr[index] = textView;  
  55.             index++;  
  56.         }  
  57.   
  58.         // 设置点击事件  
  59.         setOnClickListener(mOnClickListener);  
  60.     }  
  61. }  

(2).弹出软键盘
当LinearLayout被点击时,触发mOnClickListener。使EditText强制获取焦点,弹出键盘。
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private OnClickListener mOnClickListener = new OnClickListener() {  
  2.     @Override  
  3.     public void onClick(View v) {  
  4.         forceInputViewGetFocus();  
  5.     }  
  6. };  
  7.   
  8. // EditText强制获取焦点,弹出键盘  
  9. private void forceInputViewGetFocus() {  
  10.     mInputView.setFocusable(true);  
  11.     mInputView.setFocusableInTouchMode(true);  
  12.     mInputView.requestFocus();  
  13.     InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);  
  14.     imm.showSoftInput(mInputView, InputMethodManager.SHOW_IMPLICIT);  
  15. }  

(3).监听字符输入
使用TextWatcher监听EditText的字符输入。关键点在于使用数组保存每次输入的密码,和维持EditText内始终只显示首个密码字符。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private TextWatcher textWatcher = new TextWatcher() {  
  2.     @Override  
  3.     public void beforeTextChanged(CharSequence s, int start, int count, int after) {  
  4.     }  
  5.   
  6.     @Override  
  7.     public void onTextChanged(CharSequence s, int start, int before, int count) {  
  8.         if (s == null) {  
  9.             return;  
  10.         }  
  11.   
  12.         String newStr = s.toString();  
  13.         if (newStr.length() == 1) {// 初次输入字符,赋值给索引0  
  14.             mPasswordArr[0] = newStr;  
  15.             notifyTextChanged();  
  16.         } else if (newStr.length() == 2) {// 非首次输入字符  
  17.             // 截取第2个字符  
  18.             String newNum = newStr.substring(1);  
  19.             for (int i = 0; i < mPasswordArr.length; i++) {  
  20.                 if (mPasswordArr[i] == null) {  
  21.                     // 赋值给数组mPasswordArr,mPasswordArr中存储的就是最终的密码  
  22.                     mPasswordArr[i] = newNum;  
  23.                     // 给TextView设值  
  24.                     mViewArr[i].setText(newNum);  
  25.                     // 通知密码发生改变  
  26.                     notifyTextChanged();  
  27.                     break;  
  28.                 }  
  29.             }  
  30.             // 上述逻辑执行完毕后,把索引0的字符设置给mInputView  
  31.             // 这样,之后再输入内容时,newStr.length()始终等于2。mInputView的内容长度始终等于1  
  32.             mInputView.removeTextChangedListener(this);  
  33.             mInputView.setText(mPasswordArr[0]);  
  34.             if (mInputView.getText().length() >= 1) {  
  35.                 mInputView.setSelection(1);  
  36.             }  
  37.             mInputView.addTextChangedListener(this);  
  38.         }  
  39.     }  
  40.   
  41.     @Override  
  42.     public void afterTextChanged(Editable s) {  
  43.     }  
  44. };  

(4).监听退格键
当监听到EditText内触发退格键时,删除mPasswordArr数组内最后一个字符。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private ImeDelBugFixedEditText.OnDelKeyEventListener onDelKeyEventListener = new ImeDelBugFixedEditText  
  2.         .OnDelKeyEventListener() {  
  3.     @Override  
  4.     public void onDeleteClick() {  
  5.         for (int i = mPasswordArr.length - 1; i >= 0; i--) {  
  6.             // 找到已输入的最后一个字符,将其置为null  
  7.             if (mPasswordArr[i] != null) {  
  8.                 mPasswordArr[i] = null;  
  9.                 mViewArr[i].setText(null);  
  10.                 notifyTextChanged();  
  11.                 break;  
  12.             } else {  
  13.                 mViewArr[i].setText(null);  
  14.             }  
  15.         }  
  16.     }  
  17. };  

关键在于EditText对退格键的监听。在有的软键盘中,OnKeyListener无法监听到键盘的退格键KeyEvent.KEYCODE_DEL事件,在stackoverflow中已经有人给出了解决方法:
http://stackoverflow.com/questions/4886858/android-edittext-deletebackspace-key-event
EditText被重写后的核心代码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class ImeDelBugFixedEditText extends EditText {  
  2.     private OnDelKeyEventListener delKeyEventListener;  
  3.   
  4.     // ......  
  5.   
  6.     // 键盘弹出时被调用  
  7.     @Override  
  8.     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {  
  9.         return new ZanyInputConnection(super.onCreateInputConnection(outAttrs), true);  
  10.     }  
  11.   
  12.     private class ZanyInputConnection extends InputConnectionWrapper {  
  13.         public ZanyInputConnection(InputConnection target, boolean mutable) {  
  14.             super(target, mutable);  
  15.         }  
  16.   
  17.         // 将KeyEvent.KEYCODE_DEL事件回调出去  
  18.         @Override  
  19.         public boolean sendKeyEvent(KeyEvent event) {  
  20.             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {  
  21.                 if (delKeyEventListener != null) {  
  22.                     delKeyEventListener.onDeleteClick();  
  23.                     return true;  
  24.                 }  
  25.             }  
  26.             return super.sendKeyEvent(event);  
  27.         }  
  28.   
  29.         // deleteSurroundingText():删除字符,区间:当前光标前beforeLength个长度,当前光标后afterLength个长度  
  30.         // beforeLength == 1 && afterLength == 0表示向前删除一个字符,可视为KeyEvent.KEYCODE_DEL  
  31.         @Override  
  32.         public boolean deleteSurroundingText(int beforeLength, int afterLength) {  
  33.             if (beforeLength == 1 && afterLength == 0) {  
  34.                 return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) && sendKeyEvent(new KeyEvent  
  35.                         (KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));  
  36.             }  
  37.             return super.deleteSurroundingText(beforeLength, afterLength);  
  38.         }  
  39.     }  
  40.   
  41.     public void setDelKeyEventListener(OnDelKeyEventListener delKeyEventListener) {  
  42.         this.delKeyEventListener = delKeyEventListener;  
  43.     }  
  44.   
  45.     public interface OnDelKeyEventListener {  
  46.         void onDeleteClick();  
  47.     }  
  48. }  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值