美文网首页Android开发Android开发Android开发经验谈
自定义安全数字键盘(仿微信)

自定义安全数字键盘(仿微信)

作者: 不会飞的小猪 | 来源:发表于2018-09-25 16:25 被阅读45次

    背景

    大部分的金融App会对默认的数字键盘进行处理,以实现自定义的数字安全键盘。基于此,本文对对微信数字键盘样式进行了仿写,实现了一套自定义的数字安全键盘(支持随机数字分布)。

    一.图示效果

    GIF.gif

    二.需要考虑的问题

    1. 布局的实现方式;
      demo中使用了popupwindow,通过xml文件进行Tablayout布局。
    2. 禁掉EditText默认软键盘的弹出,替换为自定义的数字键盘及与其它EditText切换焦点时的弹出效果;
    3. 删除和增加字符时需要同步更新光标的位置;
    4. 随机数字分布的实现;

    三.实现代码

    1.MainActivity调用处代码:

    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
        private EditText numberEt;
        private KeyboardPopupWindow keyboardPopupWindow;
        private boolean isUiCreated = false;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        private void initView() {
            numberEt = findViewById(R.id.numberEt);
            keyboardPopupWindow = new KeyboardPopupWindow(MainActivity.this, getWindow().getDecorView(), numberEt,true);
    //        numberEt.setInputType(InputType.TYPE_NULL);//该设置会导致光标不可见
            numberEt.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (keyboardPopupWindow != null) {
                        keyboardPopupWindow.show();
                    }
                }
            });
            numberEt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
                @Override
                public void onFocusChange(View v, boolean hasFocus) {
                    if (keyboardPopupWindow != null && isUiCreated) {//isUiCreated 很重要,Unable to add window -- token null is not valid; is your activity running?
                        keyboardPopupWindow.refreshKeyboardOutSideTouchable(!hasFocus);// 需要等待页面创建完成后焦点变化才去显示自定义键盘
                    }
    
                    if (hasFocus) {//隐藏系统软键盘
                        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                        imm.hideSoftInputFromWindow(numberEt.getWindowToken(), 0);
                    }
    
                }
            });
        }
    
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
            isUiCreated = true;
        }
    
    
        @Override
        protected void onDestroy() {
            if (keyboardPopupWindow != null) {
                keyboardPopupWindow.releaseResources();
            }
            super.onDestroy();
        }
    }
    

    可以看到,这块的代码实现很简单,主要是通过KeyboardPopupWindow 这个自定义的View来实现键盘弹出及按键点击效果。需要注意的是isUiCreated 这个标志位,需要通过onWindowFocusChanged等方法来确定当前页面加载完毕后去刷新自定义键盘的状态,否则会报错。

    2.自定义数字键盘的代码:

    
    public class KeyboardPopupWindow extends PopupWindow {
        private static final String TAG = "KeyboardPopupWindow";
        private Context context;
        private View anchorView;
        private View parentView;
        private EditText editText;
        private boolean isRandomSort = false;//数字是否随机排序
        private List<Integer> list = new ArrayList<>();
        private int[] commonButtonIds = new int[]{R.id.button00, R.id.button01, R.id.button02, R.id.button03,
                R.id.button04, R.id.button05, R.id.button06, R.id.button07, R.id.button08, R.id.button09};
    
        /**
         * @param context
         * @param anchorView
         * @param editText
         * @param isRandomSort 数字是否随机排序
         */
        public KeyboardPopupWindow(Context context, View anchorView, EditText editText, boolean isRandomSort) {
            this.context = context;
            this.anchorView = anchorView;
            this.editText = editText;
            this.isRandomSort = isRandomSort;
            if (context == null || anchorView == null) {
                return;
            }
            initConfig();
            initView();
        }
    
    
        private void initConfig() {
            setOutsideTouchable(false);
            setFocusable(false);
            setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            forbidDefaultSoftKeyboard();
        }
    
        /**
         * 禁止系统默认的软键盘弹出
         */
        private void forbidDefaultSoftKeyboard() {
            if (editText == null) {
                return;
            }
            if (android.os.Build.VERSION.SDK_INT > 10) {//4.0以上,使用反射的方式禁止系统自带的软键盘弹出
                try {
                    Class<EditText> cls = EditText.class;
                    Method setShowSoftInputOnFocus;
                    setShowSoftInputOnFocus = cls.getMethod("setShowSoftInputOnFocus", boolean.class);
                    setShowSoftInputOnFocus.setAccessible(true);
                    setShowSoftInputOnFocus.invoke(editText, false);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 刷新自定义的popupwindow是否outside可触摸反应:如果是不可触摸的,则显示该软键盘view
         *
         * @param isTouchable
         */
        public void refreshKeyboardOutSideTouchable(boolean isTouchable) {
            setOutsideTouchable(isTouchable);
            if (!isTouchable) {
                show();
            } else {
                dismiss();
            }
        }
    
        private void initView() {
            parentView = LayoutInflater.from(context).inflate(R.layout.keyboadview, null);
            initKeyboardView(parentView);
            setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
            setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
            setContentView(parentView);
        }
    
        private void initKeyboardView(View view) {
            LinearLayout dropdownLl = view.findViewById(R.id.dropdownLl);
            dropdownLl.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dismiss();
                }
            });
    
            //①给数字键设置点击监听
            for (int i = 0; i < commonButtonIds.length; i++) {
                final Button button = view.findViewById(commonButtonIds[i]);
                button.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int curSelection = editText.getSelectionStart();
                        int length = editText.getText().toString().length();
                        if (curSelection < length) {
                            String content = editText.getText().toString();
                            editText.setText(content.substring(0, curSelection) + button.getText() + content.subSequence(curSelection, length));
                            editText.setSelection(curSelection + 1);
                        } else {
                            editText.setText(editText.getText().toString() + button.getText());
                            editText.setSelection(editText.getText().toString().length());
                        }
                    }
                });
            }
    
            //②给小数点按键设置点击监听
            view.findViewById(R.id.buttonDot).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int curSelection = editText.getSelectionStart();
                    int length = editText.getText().toString().length();
                    if (curSelection < length) {
                        String content = editText.getText().toString();
                        editText.setText(content.substring(0, curSelection) + "." + content.subSequence(curSelection, length));
                        editText.setSelection(curSelection + 1);
                    } else {
                        editText.setText(editText.getText().toString() + ".");
                        editText.setSelection(editText.getText().toString().length());
                    }
                }
            });
    
            //③给叉按键设置点击监听
            view.findViewById(R.id.buttonCross).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int length = editText.getText().toString().length();
                    int curSelection = editText.getSelectionStart();
                    if (length > 0 && curSelection > 0 && curSelection <= length) {
                        String content = editText.getText().toString();
                        editText.setText(content.substring(0, curSelection - 1) + content.subSequence(curSelection, length));
                        editText.setSelection(curSelection - 1);
                    }
                }
            });
        }
    
    
        public void show() {
            if (!isShowing() && anchorView != null) {
                doRandomSortOp();
                this.showAtLocation(anchorView, Gravity.BOTTOM, 0, 0);
            }
        }
    
        /**
         * 随机分布数字
         */
        private void doRandomSortOp() {
            if (parentView == null) {
                return;
            }
            if (!isRandomSort) {
                for (int i = 0; i < commonButtonIds.length; i++) {
                    final Button button = parentView.findViewById(commonButtonIds[i]);
                    button.setText("" + i);
                }
            } else {
                list.clear();
                Random ran = new Random();
                while (list.size() < commonButtonIds.length) {
                    int n = ran.nextInt(commonButtonIds.length);
                    if (!list.contains(n))
                        list.add(n);
                }
                for (int i = 0; i < commonButtonIds.length; i++) {
                    final Button button = parentView.findViewById(commonButtonIds[i]);
                    button.setText("" + list.get(i));
                }
            }
        }
    
        public void releaseResources() {
            this.dismiss();
            context = null;
            anchorView = null;
            if (list != null) {
                list.clear();
                list = null;
            }
        }
        
    }
    
    

    代码实现的逻辑相对简单:

    1. 通过给popupwindow设置contentView、弹出位置、边界外触摸参数等数值,实现大体样式上的效果;
    2. 给contentView中的每个button设置点击事件,并处理传递的EditText数值及焦点变化情况;
    3. 设置随机标志位,进行数值键盘数值随机的分布;
    4. forbidDefaultSoftKeyboard中处理:禁止EditText默认的软键盘弹出。因为要实现EditText焦点仍旧可见的效果,目前试过的其它集中方式仍有较大的缺陷,所以是通过反射的方法来达到目的。

    四.小结

    当然,除了以上方式,还可以使用系统的KeyboardView和Keyboard来实现相应的效果。
    在仿写的过程中会发现,实现这一效果会涉及到一些体验性的细节点,而微信正是做到了这一点,所以在使用层面上会很方便。有些细节点会可能会比实现整个大致效果更为麻烦,但有时候恰巧这些很容易被忽视的小九九是与众不同的地方。
    最后附上GitHub的源码地址:点击此处链接>>

    相关文章

      网友评论

        本文标题:自定义安全数字键盘(仿微信)

        本文链接:https://www.haomeiwen.com/subject/bweloftx.html