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

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

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

        Android自定义控件开发系列(三)——仿支付宝六位支付密码输入页面_第1张图片        Android自定义控件开发系列(三)——仿支付宝六位支付密码输入页面_第2张图片

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

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




    

        

            

            

            
        

        

        

        

            

            

            

            

            

            

            

            

            

            

            

            
        

        

        
    

    

    

 

 

 

 

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



    
    
    

 

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



    
        
            
        
    
    
        
            
        
    
    
        
            
        
    

 

        需要回退键背景selector_key_del.xml:



    
        
            
        
    
    
        
            
        
    
    
        
            
        
    

 

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

public class PasswordView extends RelativeLayout implements View.OnClickListener {
    Context context;

    private String strPassword;     //输入的密码
    private TextView[] tvList;      //用数组保存6个TextView,为什么用数组?
                                    //因为就6个输入框不会变了,用数组内存申请固定空间,比List省空间(自己认为)
    private GridView gridView;    //用GrideView布局键盘,其实并不是真正的键盘,只是模拟键盘的功能
    private ArrayList> valueList;    //有人可能有疑问,为何这里不用数组了?
                                                       //因为要用Adapter中适配,用数组不能往adapter中填充

    private ImageView imgCancel;
    private TextView tvForget;
    private int currentIndex = -1;    //用于记录当前输入密码格位置

    public PasswordView(Context context) {
        this(context, null);
    }

    public PasswordView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        View view = View.inflate(context, R.layout.layout_popup_bottom, null);
        
        valueList = new ArrayList>();
        tvList = new TextView[6];
        
        imgCancel = (ImageView) view.findViewById(R.id.img_cancel);
        imgCancel.setOnClickListener(this);

        tvForget = (TextView) view.findViewById(R.id.tv_forgetPwd);
        tvForget.setOnClickListener(this);
        
        tvList[0] = (TextView) view.findViewById(R.id.tv_pass1);
        tvList[1] = (TextView) view.findViewById(R.id.tv_pass2);
        tvList[2] = (TextView) view.findViewById(R.id.tv_pass3);
        tvList[3] = (TextView) view.findViewById(R.id.tv_pass4);
        tvList[4] = (TextView) view.findViewById(R.id.tv_pass5);
        tvList[5] = (TextView) view.findViewById(R.id.tv_pass6);

        gridView = (GridView) view.findViewById(R.id.gv_keybord);

        setView();
        
        addView(view);      //必须要,不然不显示控件
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.img_cancel:
                Toast.makeText(context, "Cancel", Toast.LENGTH_SHORT).show();
                break;
            case R.id.tv_forgetPwd:
                Toast.makeText(context, "Forget", Toast.LENGTH_SHORT).show();
                break;
        }
    }

    private void setView() {
    	/* 初始化按钮上应该显示的数字 */
        for (int i = 1; i < 13; i++) {
            Map map = new HashMap();
            if (i < 10) {
                map.put("name", String.valueOf(i));
            } else if (i == 10) {
                map.put("name", "");
            } else if (i == 12) {
                map.put("name", "<<-");
            } else if (i == 11) {
                map.put("name", String.valueOf(0));
            }
            valueList.add(map);
        }

        gridView.setAdapter(adapter);
        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position, long id) {
                if (position < 11 && position != 9) {    //点击0~9按钮
                    if (currentIndex >= -1 && currentIndex < 5) {      //判断输入位置————要小心数组越界
                        tvList[++currentIndex].setText(valueList.get(position).get("name"));
                    }
                } else {
                    if (position == 11) {      //点击退格键
                        if (currentIndex - 1 >= -1) {      //判断是否删除完毕————要小心数组越界
                            tvList[currentIndex--].setText("");
                        }
                    }
                }
            }
        });
    }

    //设置监听方法,在第6位输入完成后触发
    public void setOnFinishInput(final OnPasswordInputFinish pass) {
        tvList[5].addTextChangedListener(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) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                if (s.toString().length() == 1) {
                    strPassword = "";     //每次触发都要先将strPassword置空,再重新获取,避免由于输入删除再输入造成混乱
                    for (int i = 0; i < 6; i++) {
                        strPassword += tvList[i].getText().toString().trim();
                    }
                    pass.inputFinish();    //接口中要实现的方法,完成密码输入完成后的响应逻辑
                }
            }
        });
    }

    /* 获取输入的密码 */
    public String getStrPassword() {
        return strPassword;
    }

    /* 暴露取消支付的按钮,可以灵活改变响应 */
    public ImageView getCancelImageView() {
        return imgCancel;
    }

    /* 暴露忘记密码的按钮,可以灵活改变响应 */
    public TextView getForgetTextView() {
        return tvForget;
    }

    //GrideView的适配器
    BaseAdapter adapter = new BaseAdapter() {
        @Override
        public int getCount() {
            return valueList.size();
        }

        @Override
        public Object getItem(int position) {
            return valueList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if (convertView == null) {
                convertView = View.inflate(context, R.layout.item_gride, null);
                viewHolder = new ViewHolder();
                viewHolder.btnKey = (TextView) convertView.findViewById(R.id.btn_keys);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            viewHolder.btnKey.setText(valueList.get(position).get("name"));
            if(position == 9){
                viewHolder.btnKey.setBackgroundResource(R.drawable.selector_key_del);
                viewHolder.btnKey.setEnabled(false);
            }
            if(position == 11){
                viewHolder.btnKey.setBackgroundResource(R.drawable.selector_key_del);
            }

            return convertView;
        }
    };

    /**
     * 存放控件
     */
    public final class ViewHolder {
        public TextView btnKey;
    }
}

 

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

/**
 * Belong to the Project —— MyPayUI 
 * Created by WangJ on 2015/11/25 17:15.
 * 
 * 自定义接口,用于给密码输入完成添加回掉事件
 */
public interface OnPasswordInputFinish {
	void inputFinish();
}

 

 

 

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




    
    

 

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

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

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        /************* 第一种用法————开始 ***************/
        setContentView(R.layout.activity_main);

        final PasswordView pwdView = (PasswordView) findViewById(R.id.pwd_view);
        
        //添加密码输入完成的响应
        pwdView.setOnFinishInput(new OnPasswordInputFinish() {
            @Override
            public void inputFinish() {
            	//输入完成后我们简单显示一下输入的密码
            	//也就是说——>实现你的交易逻辑什么的在这里写
                Toast.makeText(MainActivity.this, pwdView.getStrPassword(), Toast.LENGTH_SHORT).show();
            }
        });
        
        /**
         *  可以用自定义控件中暴露出来的cancelImageView方法,重新提供相应
         *  如果写了,会覆盖我们在自定义控件中提供的响应
         *  可以看到这里toast显示 "Biu Biu Biu"而不是"Cancel"*/
        pwdView.getCancelImageView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "Biu Biu Biu", Toast.LENGTH_SHORT).show();
            }
        });
        /************ 第一种用法————结束 ******************/

        
        /************* 第二种用法————开始 *****************/
//        final PasswordView pwdView = new PasswordView(this);
//        setContentView(pwdView);
//        pwdView.setOnFinishInput(new OnPasswordInputFinish() {
//            @Override
//            public void inputFinish() {
//                Toast.makeText(MainActivity.this, pwdView.getStrPassword(), Toast.LENGTH_SHORT).show();
//            }
//        });
        /************** 第二种用法————结束 ****************/
    }
}

 

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




    

 

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

Android自定义控件开发系列(三)——仿支付宝六位支付密码输入页面_第3张图片        Android自定义控件开发系列(三)——仿支付宝六位支付密码输入页面_第4张图片

        肯定有人有疑问,为什么两个差别这么大?为什么这里不想支付宝一样是从底部向上弹出?这里我需要解释一下:

    (1)我们这里只是完成了这样一个控件,至于像支付宝一样从底部向上弹出,需要借助别的方法,比如用PopupWindow将控件包裹进行弹窗等方法,我们这里只是显示出来验证一下;

    (2)我们没有单独定义自己的密码键盘,只是模拟了一下键盘的功能,所以密码输入框和键盘不能分离,如果有需求,你要单独定义安全键盘;

    (3)这里每个按钮我是用一个只包含TextView的布局模拟的,你可以根据需求进行更改,甚至每个按钮都可以自定义,我这里只是提供一种实现思路。

        **在自定义控件的问题上如有疑问,欢迎查看之前的自定义控件系列(零)——原理篇、自定义控件系列(一)、自定义控件系列(二)。

        以上是我自己想到的实现方式,如果各位有更好的方法,麻烦留言教教我啊,程序猿大人再次先谢了水平有限,如有不足,欢迎指出

        附上源码下载地址——免积分哦——Android仿支付宝支付密码界面源码下载

        修改后的Demo地址——Android仿支付宝支付密码自定义控件界面_修正显示

你可能感兴趣的:(Android控件-自定义)