Android自定义控件开发系列——仿支付宝六位支付密码输入

来源:互联网 发布:双十一 淘宝 消费人群 编辑:程序博客网 时间:2024/05/21 17:18

   在移动互联领域,有那么几家龙头一直是我等学习和追求的目标,比如支付宝、微信、饿了么、酷狗音乐等等,大神举不胜举,他们设计的界面、交互方式已经培养了中国(有可能会是世界)民众的操作习惯:举个小例子,对话框“确定”按钮的左右位置就很有学问,如果大家都是左边取消右边确定,你的作品偏偏相反,就会导致用户在操作时候很不适应,甚至会习惯性点错,这一小小的问题将严重影响产品的体验,闲话少说,开始今天的主题。

        今天来模仿一下支付宝6位支付密码的输入控件。

        IOS支付宝6位支付密码        我实现的效果

        我们先来照图分析一下:(1)限制输入6位,每一位都有自己的框格,每个格显示一位;(2)有回退/取消支付按钮;(3)有忘记密码链接;(4)自定义的只能输入数字的键盘输入区;(5)在6位输完后自动进行密码校验和支付交易。如上图左边是iOS支付宝支付密码输入控件,右边是我模仿实现的效果。下边我们来一步一步完成这样的效果:

        首先,我们需要一个页面来完成以上的静态布局,.xml代码如下:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="#FFFFFF"  
  6.     android:gravity="bottom" >  
  7.   
  8.     <LinearLayout  
  9.         android:id="@+id/linear_pass"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content"  
  12.         android:orientation="vertical" >  
  13.   
  14.         <RelativeLayout  
  15.             android:layout_width="match_parent"  
  16.             android:layout_height="wrap_content"  
  17.             android:layout_margin="5dp" >  
  18.   
  19.             <!-- 取消按钮 -->  
  20.   
  21.             <ImageView  
  22.                 android:id="@+id/img_cancel"  
  23.                 android:layout_width="wrap_content"  
  24.                 android:layout_height="wrap_content"  
  25.                 android:background="@drawable/icon_clean" />  
  26.   
  27.             <TextView  
  28.                 android:layout_width="wrap_content"  
  29.                 android:layout_height="wrap_content"  
  30.                 android:layout_centerInParent="true"  
  31.                 android:text="输入密码"  
  32.                 android:textColor="#898181"  
  33.                 android:textSize="20sp" />  
  34.         </RelativeLayout>  
  35.   
  36.         <View  
  37.             android:layout_width="match_parent"  
  38.             android:layout_height="0.5dp"  
  39.             android:background="#555555" />  
  40.   
  41.         <!-- 6位密码框布局,需要一个圆角边框的shape作为layout的背景 -->  
  42.   
  43.         <LinearLayout  
  44.             android:layout_width="match_parent"  
  45.             android:layout_height="wrap_content"  
  46.             android:layout_marginLeft="40dp"  
  47.             android:layout_marginRight="40dp"  
  48.             android:layout_marginTop="20dp"  
  49.             android:background="@drawable/shape_input_area"  
  50.             android:orientation="horizontal" >  
  51.   
  52.             <!--  
  53.                  inputType设置隐藏密码明文    
  54.                  textSize设置大一点,否则“点”太小了,不美观  
  55.             -->  
  56.   
  57.             <TextView  
  58.                 android:id="@+id/tv_pass1"  
  59.                 android:layout_width="0dp"  
  60.                 android:layout_height="wrap_content"  
  61.                 android:layout_weight="1"  
  62.                 android:gravity="center"  
  63.                 android:inputType="numberPassword"  
  64.                 android:textSize="32sp" />  
  65.   
  66.             <View  
  67.                 android:layout_width="1dp"  
  68.                 android:layout_height="match_parent"  
  69.                 android:background="#999999" />  
  70.   
  71.             <TextView  
  72.                 android:id="@+id/tv_pass2"  
  73.                 android:layout_width="0dp"  
  74.                 android:layout_height="wrap_content"  
  75.                 android:layout_weight="1"  
  76.                 android:gravity="center"  
  77.                 android:inputType="numberPassword"  
  78.                 android:textSize="32sp" />  
  79.   
  80.             <View  
  81.                 android:layout_width="1dp"  
  82.                 android:layout_height="match_parent"  
  83.                 android:background="#999999" />  
  84.   
  85.             <TextView  
  86.                 android:id="@+id/tv_pass3"  
  87.                 android:layout_width="0dp"  
  88.                 android:layout_height="wrap_content"  
  89.                 android:layout_weight="1"  
  90.                 android:gravity="center"  
  91.                 android:inputType="numberPassword"  
  92.                 android:textSize="32sp" />  
  93.   
  94.             <View  
  95.                 android:layout_width="1dp"  
  96.                 android:layout_height="match_parent"  
  97.                 android:background="#999999" />  
  98.   
  99.             <TextView  
  100.                 android:id="@+id/tv_pass4"  
  101.                 android:layout_width="0dp"  
  102.                 android:layout_height="wrap_content"  
  103.                 android:layout_weight="1"  
  104.                 android:gravity="center"  
  105.                 android:inputType="numberPassword"  
  106.                 android:textSize="32sp" />  
  107.   
  108.             <View  
  109.                 android:layout_width="1dp"  
  110.                 android:layout_height="match_parent"  
  111.                 android:background="#999999" />  
  112.   
  113.             <TextView  
  114.                 android:id="@+id/tv_pass5"  
  115.                 android:layout_width="0dp"  
  116.                 android:layout_height="wrap_content"  
  117.                 android:layout_weight="1"  
  118.                 android:gravity="center"  
  119.                 android:inputType="numberPassword"  
  120.                 android:textSize="32sp" />  
  121.   
  122.             <View  
  123.                 android:layout_width="1dp"  
  124.                 android:layout_height="match_parent"  
  125.                 android:background="#999999" />  
  126.   
  127.             <TextView  
  128.                 android:id="@+id/tv_pass6"  
  129.                 android:layout_width="0dp"  
  130.                 android:layout_height="wrap_content"  
  131.                 android:layout_weight="1"  
  132.                 android:gravity="center"  
  133.                 android:inputType="numberPassword"  
  134.                 android:textSize="32sp" />  
  135.         </LinearLayout>  
  136.   
  137.         <!-- 忘记密码链接 -->  
  138.   
  139.         <TextView  
  140.             android:id="@+id/tv_forgetPwd"  
  141.             android:layout_width="wrap_content"  
  142.             android:layout_height="wrap_content"  
  143.             android:layout_gravity="right"  
  144.             android:layout_margin="15dp"  
  145.             android:text="忘记密码?"  
  146.             android:textColor="#354EEF" />  
  147.     </LinearLayout>  
  148.   
  149.     <!-- 输入键盘 -->  
  150.   
  151.     <GridView  
  152.         android:id="@+id/gv_keybord"  
  153.         android:layout_width="match_parent"  
  154.         android:layout_height="wrap_content"  
  155.         android:layout_below="@id/linear_pass"  
  156.         android:layout_marginTop="40dp"  
  157.         android:background="@android:color/black"  
  158.         android:horizontalSpacing="0.5dp"  
  159.         android:listSelector="@null"  
  160.         android:numColumns="3"  
  161.         android:verticalSpacing="0.5dp" /><!-- android:listSelector="@null"取消系统自带的按下效果,否则模拟键盘外围会有黑边 -->  
  162.   
  163. </RelativeLayout>  

        其中需要圆角背景shape_input_area.xml:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android">  
  3.     <corners android:radius="5dp"/>  
  4.     <stroke android:color="@android:color/darker_gray"  
  5.         android:width="1dp"/>  
  6.     <solid android:color="@android:color/white"/>  
  7. </shape>  

        需要数字按钮的背景selector_gride.xml:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
  3.     <item android:state_enabled="false">  
  4.         <shape>  
  5.             <solid android:color="#C0C4C7" />  
  6.         </shape>  
  7.     </item>  
  8.     <item android:state_enabled="true" android:state_pressed="false">  
  9.         <shape>  
  10.             <solid android:color="@android:color/white" />  
  11.         </shape>  
  12.     </item>  
  13.     <item android:state_enabled="true" android:state_pressed="true">  
  14.         <shape>  
  15.             <solid android:color="#C0C4C7" />  
  16.         </shape>  
  17.     </item>  
  18. </selector>  

        需要回退键背景selector_key_del.xml:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
  3.     <item android:state_enabled="false">  
  4.         <shape>  
  5.             <solid android:color="#C0C4C7" />  
  6.         </shape>  
  7.     </item>  
  8.     <item android:state_enabled="true" android:state_pressed="false">  
  9.         <shape>  
  10.             <solid android:color="#C0C4C7" />  
  11.         </shape>  
  12.     </item>  
  13.     <item android:state_enabled="true" android:state_pressed="true">  
  14.         <shape>  
  15.             <solid android:color="@android:color/white" />  
  16.         </shape>  
  17.     </item>  
  18. </selector>  

        下面来完成我们的自定义控件PasswordView.Java:

[java] view plain copy
  1. public class PasswordView extends RelativeLayout implements View.OnClickListener {  
  2.     Context context;  
  3.   
  4.     private String strPassword;     //输入的密码  
  5.     private TextView[] tvList;      //用数组保存6个TextView,为什么用数组?  
  6.                                     //因为就6个输入框不会变了,用数组内存申请固定空间,比List省空间(自己认为)  
  7.     private GridView gridView;    //用GrideView布局键盘,其实并不是真正的键盘,只是模拟键盘的功能  
  8.     private ArrayList<Map<String, String>> valueList;    //有人可能有疑问,为何这里不用数组了?  
  9.                                                        //因为要用Adapter中适配,用数组不能往adapter中填充  
  10.   
  11.     private ImageView imgCancel;  
  12.     private TextView tvForget;  
  13.     private int currentIndex = -1;    //用于记录当前输入密码格位置  
  14.   
  15.     public PasswordView(Context context) {  
  16.         this(context, null);  
  17.     }  
  18.   
  19.     public PasswordView(Context context, AttributeSet attrs) {  
  20.         super(context, attrs);  
  21.         this.context = context;  
  22.         View view = View.inflate(context, R.layout.layout_popup_bottom, null);  
  23.           
  24.         valueList = new ArrayList<Map<String, String>>();  
  25.         tvList = new TextView[6];  
  26.           
  27.         imgCancel = (ImageView) view.findViewById(R.id.img_cancel);  
  28.         imgCancel.setOnClickListener(this);  
  29.   
  30.         tvForget = (TextView) view.findViewById(R.id.tv_forgetPwd);  
  31.         tvForget.setOnClickListener(this);  
  32.           
  33.         tvList[0] = (TextView) view.findViewById(R.id.tv_pass1);  
  34.         tvList[1] = (TextView) view.findViewById(R.id.tv_pass2);  
  35.         tvList[2] = (TextView) view.findViewById(R.id.tv_pass3);  
  36.         tvList[3] = (TextView) view.findViewById(R.id.tv_pass4);  
  37.         tvList[4] = (TextView) view.findViewById(R.id.tv_pass5);  
  38.         tvList[5] = (TextView) view.findViewById(R.id.tv_pass6);  
  39.   
  40.         gridView = (GridView) view.findViewById(R.id.gv_keybord);  
  41.   
  42.         setView();  
  43.           
  44.         addView(view);      //必须要,不然不显示控件  
  45.     }  
  46.   
  47.     @Override  
  48.     public void onClick(View v) {  
  49.         switch (v.getId()) {  
  50.             case R.id.img_cancel:  
  51.                 Toast.makeText(context, "Cancel", Toast.LENGTH_SHORT).show();  
  52.                 break;  
  53.             case R.id.tv_forgetPwd:  
  54.                 Toast.makeText(context, "Forget", Toast.LENGTH_SHORT).show();  
  55.                 break;  
  56.         }  
  57.     }  
  58.   
  59.     private void setView() {  
  60.         /* 初始化按钮上应该显示的数字 */  
  61.         for (int i = 1; i < 13; i++) {  
  62.             Map<String, String> map = new HashMap<String, String>();  
  63.             if (i < 10) {  
  64.                 map.put("name", String.valueOf(i));  
  65.             } else if (i == 10) {  
  66.                 map.put("name""");  
  67.             } else if (i == 12) {  
  68.                 map.put("name""<<-");  
  69.             } else if (i == 11) {  
  70.                 map.put("name", String.valueOf(0));  
  71.             }  
  72.             valueList.add(map);  
  73.         }  
  74.   
  75.         gridView.setAdapter(adapter);  
  76.         gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
  77.             @Override  
  78.             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
  79.                 if (position < 11 && position != 9) {    //点击0~9按钮  
  80.                     if (currentIndex >= -1 && currentIndex < 5) {      //判断输入位置————要小心数组越界  
  81.                         tvList[++currentIndex].setText(valueList.get(position).get("name"));  
  82.                     }  
  83.                 } else {  
  84.                     if (position == 11) {      //点击退格键  
  85.                         if (currentIndex - 1 >= -1) {      //判断是否删除完毕————要小心数组越界  
  86.                             tvList[currentIndex--].setText("");  
  87.                         }  
  88.                     }  
  89.                 }  
  90.             }  
  91.         });  
  92.     }  
  93.   
  94.     //设置监听方法,在第6位输入完成后触发  
  95.     public void setOnFinishInput(final OnPasswordInputFinish pass) {  
  96.         tvList[5].addTextChangedListener(new TextWatcher() {  
  97.             @Override  
  98.             public void beforeTextChanged(CharSequence s, int start, int count, int after) {  
  99.   
  100.             }  
  101.   
  102.             @Override  
  103.             public void onTextChanged(CharSequence s, int start, int before, int count) {  
  104.   
  105.             }  
  106.   
  107.             @Override  
  108.             public void afterTextChanged(Editable s) {  
  109.                 if (s.toString().length() == 1) {  
  110.                     strPassword = "";     //每次触发都要先将strPassword置空,再重新获取,避免由于输入删除再输入造成混乱  
  111.                     for (int i = 0; i < 6; i++) {  
  112.                         strPassword += tvList[i].getText().toString().trim();  
  113.                     }  
  114.                     pass.inputFinish();    //接口中要实现的方法,完成密码输入完成后的响应逻辑  
  115.                 }  
  116.             }  
  117.         });  
  118.     }  
  119.   
  120.     /* 获取输入的密码 */  
  121.     public String getStrPassword() {  
  122.         return strPassword;  
  123.     }  
  124.   
  125.     /* 暴露取消支付的按钮,可以灵活改变响应 */  
  126.     public ImageView getCancelImageView() {  
  127.         return imgCancel;  
  128.     }  
  129.   
  130.     /* 暴露忘记密码的按钮,可以灵活改变响应 */  
  131.     public TextView getForgetTextView() {  
  132.         return tvForget;  
  133.     }  
  134.   
  135.     //GrideView的适配器  
  136.     BaseAdapter adapter = new BaseAdapter() {  
  137.         @Override  
  138.         public int getCount() {  
  139.             return valueList.size();  
  140.         }  
  141.   
  142.         @Override  
  143.         public Object getItem(int position) {  
  144.             return valueList.get(position);  
  145.         }  
  146.   
  147.         @Override  
  148.         public long getItemId(int position) {  
  149.             return position;  
  150.         }  
  151.   
  152.         @Override  
  153.         public View getView(int position, View convertView, ViewGroup parent) {  
  154.             ViewHolder viewHolder;  
  155.             if (convertView == null) {  
  156.                 convertView = View.inflate(context, R.layout.item_gride, null);  
  157.                 viewHolder = new ViewHolder();  
  158.                 viewHolder.btnKey = (TextView) convertView.findViewById(R.id.btn_keys);  
  159.                 convertView.setTag(viewHolder);  
  160.             } else {  
  161.                 viewHolder = (ViewHolder) convertView.getTag();  
  162.             }  
  163.             viewHolder.btnKey.setText(valueList.get(position).get("name"));  
  164.             if(position == 9){  
  165.                 viewHolder.btnKey.setBackgroundResource(R.drawable.selector_key_del);  
  166.                 viewHolder.btnKey.setEnabled(false);  
  167.             }  
  168.             if(position == 11){  
  169.                 viewHolder.btnKey.setBackgroundResource(R.drawable.selector_key_del);  
  170.             }  
  171.   
  172.             return convertView;  
  173.         }  
  174.     };  
  175.   
  176.     /** 
  177.      * 存放控件 
  178.      */  
  179.     public final class ViewHolder {  
  180.         public TextView btnKey;  
  181.     }  
  182. }  

        自认为代码注释还是可以的。就是在实现过程中要注意数组的越界问题,在输入逻辑响应中要注意逻辑处理,也就是grideView的OnItemClickListener事件处理。其中用到自定义的接口OnPasswordInputFinish来实现输入完成的事件回掉:

[java] view plain copy
  1. /** 
  2.  * Belong to the Project —— MyPayUI  
  3.  * Created by WangJ on 2015/11/25 17:15. 
  4.  *  
  5.  * 自定义接口,用于给密码输入完成添加回掉事件 
  6.  */  
  7. public interface OnPasswordInputFinish {  
  8.     void inputFinish();  
  9. }  

        还有就是Adapter中用到的每个按钮Item的布局item_gride.xml:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent">  
  5.   
  6.     <!-- 模拟键盘按钮,当然你可以用Button,但要注意Button和GrideView的点击响应问题 -->  
  7.     <TextView  
  8.         android:id="@+id/btn_keys"  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent"  
  11.         android:padding="10dp"  
  12.         android:gravity="center"  
  13.         android:textSize="25sp"  
  14.         android:background="@drawable/selector_gride"/>  
  15. </LinearLayout>  

        好了,到此我们的自定义控件——模仿支付宝6位支付密码输入控件就完成了,下边我们在Activity中用一下,检验一下效果:

        我们在MianActivity中用用一下我们定义好的控件:

[java] view plain copy
  1. public class MainActivity extends Activity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.           
  7.         /************* 第一种用法————开始 ***************/  
  8.         setContentView(R.layout.activity_main);  
  9.   
  10.         final PasswordView pwdView = (PasswordView) findViewById(R.id.pwd_view);  
  11.           
  12.         //添加密码输入完成的响应  
  13.         pwdView.setOnFinishInput(new OnPasswordInputFinish() {  
  14.             @Override  
  15.             public void inputFinish() {  
  16.                 //输入完成后我们简单显示一下输入的密码  
  17.                 //也就是说——>实现你的交易逻辑什么的在这里写  
  18.                 Toast.makeText(MainActivity.this, pwdView.getStrPassword(), Toast.LENGTH_SHORT).show();  
  19.             }  
  20.         });  
  21.           
  22.         /** 
  23.          *  可以用自定义控件中暴露出来的cancelImageView方法,重新提供相应 
  24.          *  如果写了,会覆盖我们在自定义控件中提供的响应 
  25.          *  可以看到这里toast显示 "Biu Biu Biu"而不是"Cancel"*/  
  26.         pwdView.getCancelImageView().setOnClickListener(new View.OnClickListener() {  
  27.             @Override  
  28.             public void onClick(View v) {  
  29.                 Toast.makeText(MainActivity.this"Biu Biu Biu", Toast.LENGTH_SHORT).show();  
  30.             }  
  31.         });  
  32.         /************ 第一种用法————结束 ******************/  
  33.   
  34.           
  35.         /************* 第二种用法————开始 *****************/  
  36. //        final PasswordView pwdView = new PasswordView(this);  
  37. //        setContentView(pwdView);  
  38. //        pwdView.setOnFinishInput(new OnPasswordInputFinish() {  
  39. //            @Override  
  40. //            public void inputFinish() {  
  41. //                Toast.makeText(MainActivity.this, pwdView.getStrPassword(), Toast.LENGTH_SHORT).show();  
  42. //            }  
  43. //        });  
  44.         /************** 第二种用法————结束 ****************/  
  45.     }  
  46. }  

        在第一种方法中我们用到的布局文件:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout  
  3.     android:id="@+id/xxx"  
  4.     xmlns:android="http://schemas.android.com/apk/res/android"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:background="#624762">  
  8.   
  9.     <com.wangj.mypayview.PasswordView  
  10.         android:id="@+id/pwd_view"  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="wrap_content"  
  13.         android:layout_alignParentBottom="true"/>  
  14. </RelativeLayout>  

        看图(左方法一、右方法二):

        

阅读全文
0 0
原创粉丝点击