仿支付宝密码输入以及细节总结

网上已经有很多现成的轮子了,虽然说重复造轮子不好,但是对于初学者还是多写多实现,了解原理最重要。

首先看下效果:

这里写图片描述

需求

主要有以下几个点:

  • 密码输入框由几个方框组成

  • 当在软键盘上输入密码时,在对应的方框内会用圆点作为代替

  • 当输入完成以后,自动进行验证和执行操作

  • 点击密码框时自动弹出软键盘,并且软键盘是数字键盘,输入其他字符无效,当焦点变化时自动隐藏软键盘

  • 支持删除操作

分析

有三种实现方式:

  • 在一个LinearLayout中布局几个方框,每个方框都是可编辑的,再对输入监听

  • 继承自EditText,绘制方框并且去除下划线等,对输入监听

  • 直接继承自View,全定制,本文采用这种方法

对于方框本文采用先绘制整体的外围矩形,再通过循环绘制每一格的分割线。

对于圆点,可以动态绘制,通过监听输入数据,获取总的数据的大小,从而重绘。

对于删除和软键盘的弹入弹出,在下面边附代码边解释。

代码详解

方框的绘制

由于View中的方法很多都是返回的px,所以在View中去设定方框的长宽以及分隔线的大小并不好,建议在格式xml文件中定义,然后在布局xml中设置并赋值,在View的构造函数中导入,这样的设置耦合度小,也易于控制。

首先在ViewonSizeChanged()方法中获取视图的宽高,设置方框的大小:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        textHeight = h;
        textWidth = w;
        rectF.set(0, 0, textWidth, textHeight);
    }

onDraw方法中绘制方框和分隔线:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRoundRect(rectF, rx, ry, textRectPaint);
        for (int i=1; i<numberCount; i++){
            canvas.drawLine(i*textWidth/numberCount, 0, i*textWidth/numberCount, textHeight, textRectPaint);
        }
        drawCircle(canvas);
    }

此外还有一个循环绘制圆点的方法,通过获取数据的大小动态通知重绘:

    private void drawCircle(Canvas canvas){
        if (array.size()==0) {
            return;
        }
        for (int i=1; i<=array.size(); i++){
            canvas.drawCircle((float)((i-0.5)*textWidth/numberCount), textHeight/2, 20, pointPaint);
        }
    }

方框的绘制很简单,主要就是注意视图大小的获取是在onSizeChanged方法中,该方法在视图布局变化时回调,在ViewonMeasurelayout方法执行后会回调。

此外也可以在layout方法中通过下面的方式获取,注意必须在onMeasure方法之后调用

textWidth = getMeasuredWidth();
textHeight = getMeasuredHeight();

也可以在layout方法之后通过getWidth()getHeight()方法获取视图的长宽,具体原理见我的另一篇文章:《android中各种height和width总结》

软键盘的触摸弹起和失去焦点隐藏

首先必须将该View设置为可获得焦点并且在触摸模式下也是可以获得焦点的,触摸模式下获得焦点意味着在支持键盘输入并且软键盘输入的时候该View仍然持有焦点,想要能够获取用户输入必须设置此属性,在布局xml文件中添加如下两行:

android:focusable="true"
android:focusableInTouchMode="true"

也可以在java代码中设置view.requestFocus()view.setFocusableInTouchMode(true),优先级比xml中要高。

设置好了以后就可以获取触摸事件弹起软键盘了:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            input.showSoftInput(this, InputMethodManager.SHOW_FORCED);
            return true;
        }
        return super.onTouchEvent(event);
    }

input的初始化如下:

input = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);

上面的SHOW_FORCED意思是强制弹出软键盘。下面看失去焦点后如何隐藏软键盘。

这里有两种方法,一种是在根布局中设置点击事件监听器,如果点击事件发生的View为根布局,就隐藏软键盘,这种适合控件很少且不需要获取单击事件的情况,具体代码如下:

@Override  
public void onClick(View v) {  
    switch (v.getId()) {  
    case R.id.traceroute_rootview:  
         InputMethodManager imm = (InputMethodManager)  
         getSystemService(Context.INPUT_METHOD_SERVICE);  
         imm.hideSoftInputFromWindow(v.getWindowToken(), 0);  
        break;  
    }  
}  

第二种方法是通过Activity的事件分发机制,获取当前焦点所在的View,也就是弹出软键盘所在的View,判断触摸事件是否发生在View的范围以内,否就不处理,是就收起软键盘。代码如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction()==MotionEvent.ACTION_DOWN) {
            touchView = getCurrentFocus();
            if (isShouldHideInput(touchView, ev)) {
                input = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                if (input!=null) {
                    input.hideSoftInputFromWindow(touchView.getWindowToken(), 0);
                }
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    private boolean isShouldHideInput(View view, MotionEvent event){
        if (view!=null) {
            int[] locations = new int[2];
            view.getLocationInWindow(locations);
            int left = locations[0];
            int top = locations[1];
            int right = left+view.getWidth();
            int bottom = top+view.getHeight();
            if (event.getRawX()>=left && event.getRawX()<=right && event.getRawY()>=top && event.getRawY()<=bottom) {
                return false;
            }
            return true;
        }
        return false;
    }

注意此处用getRawX(),因为前面是获取当前Window下的坐标,所以不是相对于父容器的坐标。执行完以后调用父类的方法,继续分发事件,经验证不影响其他控件的触摸事件。

限定弹出键盘的类型

为了更好的用户体验,弹出的键盘需要是数字键盘,避免用户手动切换,代码如下:

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;
        outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
        return super.onCreateInputConnection(outAttrs);
    }

这里的inputType规定了弹出键盘是NUMBER类型。具体见博客:Android控件TextView的实现原理分析

键盘输入

弹出键盘以后,我们就可以输入密码了,但是我们还需要进行判断,对不是数字的输入不作响应。每次输入一个数字,就将此数字加入到List中,一旦输入完以后自动进行验证和交易,这里我就用一个Toast来代表验证过程了。删除可以通过监听删除键对List进行操作。具体监听是继承View.OnKeyListener类重写onKey方法。代码如下:

    class MyOnKeyListener implements View.OnKeyListener{

        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (keyCode>=KeyEvent.KEYCODE_0 && keyCode<=KeyEvent.KEYCODE_9) {
                    if (list.size()<numberCount) {
                        list.add(keyCode-7);
                        invalidate();
                        if (list.size()==numberCount) {
                            ensureFinishInput();

                            //输入结束后收起键盘
                            input.hideSoftInputFromWindow(MyEditText.this.getWindowToken(), 0);
                        }
                    }
                    return true;
                }
                if (keyCode==KeyEvent.KEYCODE_DEL) {
                    if (list.size()!=0) {
                        list.remove(list.size()-1);
                        invalidate();
                    }
                    return true;
                }
            }
            return false;
        }

        private void ensureFinishInput(){
            StringBuffer sb = new StringBuffer();
            sb.append(list.toString());
            Toast.makeText(getContext(),sb.toString(),Toast.LENGTH_SHORT).show();
        }
    }

总结一下,其实看上去挺简单的,但是对于新手来说操作起来还是有很多细节的,不多说,继续学习!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值