转自:http://blog.csdn.net/dong_18383219470/article/details/52639066
我们先来照图分析一下:
(1)限制输入6位,每一位都有自己的框格,每个格显示一位;
(2)有回退/取消支付按钮;
(3)有忘记密码链接;
(4)自定义的只能输入数字的键盘输入区;
(5)在6位输完后自动进行密码校验和支付交易。如上图左边是iOS支付宝支付密码输入控件,右边是我模仿实现的效果。
首先,我们需要一个页面来完成以上的静态布局,.xml代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#EEEEEE"
- android:gravity="bottom">
- <LinearLayout
- android:id="@+id/linear_pass"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="5dp">
- <!-- 取消按钮 -->
- <ImageView
- android:id="@+id/img_cancel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@drawable/icon_clean" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:text="输入密码"
- android:textColor="#898181"
- android:textSize="20sp" />
- </RelativeLayout>
- <View
- android:layout_width="match_parent"
- android:layout_height="0.5dp"
- android:background="#555555" />
- <!-- 6位密码框布局,需要一个圆角边框的shape作为layout的背景 -->
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="40dp"
- android:layout_marginRight="40dp"
- android:layout_marginTop="20dp"
- android:background="@drawable/shape_input_area"
- android:orientation="horizontal">
- <!-- inputType设置隐藏密码明文
- textSize设置大一点,否则“点”太小了,不美观 -->
- <TextView
- android:id="@+id/tv_pass1"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="center"
- android:inputType="numberPassword"
- android:textSize="32sp" />
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#999999" />
- <TextView
- android:id="@+id/tv_pass2"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="center"
- android:inputType="numberPassword"
- android:textSize="32sp" />
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#999999" />
- <TextView
- android:id="@+id/tv_pass3"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="center"
- android:inputType="numberPassword"
- android:textSize="32sp" />
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#999999" />
- <TextView
- android:id="@+id/tv_pass4"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="center"
- android:inputType="numberPassword"
- android:textSize="32sp" />
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#999999" />
- <TextView
- android:id="@+id/tv_pass5"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="center"
- android:inputType="numberPassword"
- android:textSize="32sp" />
- <View
- android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#999999" />
- <TextView
- android:id="@+id/tv_pass6"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:gravity="center"
- android:inputType="numberPassword"
- android:textSize="32sp" />
- </LinearLayout>
- <!-- 忘记密码链接 -->
- <TextView
- android:id="@+id/tv_forgetPwd"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="right"
- android:layout_margin="15dp"
- android:text="忘记密码?"
- android:textColor="#354EEF" />
- </LinearLayout>
- <!-- 输入键盘 -->
- <GridView
- android:id="@+id/gv_keybord"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/linear_pass"
- android:layout_marginTop="40dp"
- android:background="@android:color/black"
- android:horizontalSpacing="0.5dp"
- android:numColumns="3"
- android:verticalSpacing="0.5dp" />
- </RelativeLayout>
- <?xml version="1.0" encoding="utf-8"?>
- <shape xmlns:android="http://schemas.android.com/apk/res/android">
- <corners android:radius="5dp"/>
- <stroke android:color="@android:color/darker_gray"
- android:width="1dp"/>
- <solid android:color="@android:color/white"/>
- </shape>
- <?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false">
- <shape>
- <solid android:color="#C0C4C7" />
- </shape>
- </item>
- <item android:state_enabled="true" android:state_pressed="false">
- <shape>
- <solid android:color="@android:color/white" />
- </shape>
- </item>
- <item android:state_enabled="true" android:state_pressed="true">
- <shape>
- <solid android:color="#C0C4C7" />
- </shape>
- </item>
- </selector>
- <?xml version="1.0" encoding="utf-8"?>
- <selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false">
- <shape>
- <solid android:color="#C0C4C7" />
- </shape>
- </item>
- <item android:state_enabled="true" android:state_pressed="false">
- <shape>
- <solid android:color="#C0C4C7" />
- </shape>
- </item>
- <item android:state_enabled="true" android:state_pressed="true">
- <shape>
- <solid android:color="@android:color/white" />
- </shape>
- </item>
- </selector>
- 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<Map<String, String>> 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<Map<String, String>>();
- tvList = new TextView[6];
- imgCancel = (ImageView) view.findViewById(R.id.img_cancel);
- imgCancel.setOnClickListener(this);
- tvForget = (TextView) 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<String, String> map = new HashMap<String, String>();
- 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();
- }
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <!-- 模拟键盘按钮,当然你可以用Button,但要注意Button和GrideView的点击响应问题 -->
- <TextView
- android:id="@+id/btn_keys"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="10dp"
- android:gravity="center"
- android:textSize="25sp"
- android:background="@drawable/selector_gride"/>
- </LinearLayout>
好了,到此我们的自定义控件——模仿支付宝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();
- // }
- // });
- /************** 第二种用法————结束 ****************/
- }
- }
在第一种方法中我们用到的布局文件:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout
- android:id="@+id/xxx"
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#624762">
- <com.wangj.mypayview.PasswordView
- android:id="@+id/pwd_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"/>
- </RelativeLayout>