仿支付宝支付键盘

第一次拿到这个需求,第一个想法,各种控件嵌套+监听 解决问题。后来想想,这么个东西用这么多控件有点大材小用了,于是就自定义了。
前沿:由于大部分程序员的特性以及工作性质都属于拿来主义者。特此说明,本文章只提供解决思路和关键性代码,不会附带全部代码。

由于只是已Demo方式呈现,并不是一个成熟的自定义控件,好多属性都没有抽离出来,项目写死了。当然也好改。
第一步:构造。这个没什么可说的,在里面初始化一些东西。

 public PasswordView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }
    private void init(final Context context, AttributeSet attrs) {
        //初始化画笔相关

        int textColor = 0x66666666;
        int forgetColor = 0xFF0000FF;
        linePaint = PaintFactory.createAntAndDitherPaint(lineWidth, textColor);
        //创建一个粗体TextPaint
        textPaint = PaintFactory.createBoldTextPaint(lineWidth, textColor);
        textPaint.setTextSize(50);
        //创建一个忘记密码的Paint
        forgetPasswordPaint = PaintFactory.createNormalTextPaint(lineWidth, forgetColor);
        forgetPasswordPaint.setTextSize(30);
        //输入框 外表 shape相关
        inputRoundPaint = PaintFactory.createStrokeRoundPaint(lineWidth, textColor);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordView);


        a.recycle();
        }

这个不是ViewGroup不需要去排版直接走onMeasure。处于测试阶段,按照作者的手机屏幕来的。实际情况,应当获取到具体值。如果你要问,作者为啥不写。答复:故意的。(不要查水表)。重点就那么几点,知道MeasureSpec的几种常量值,明白他是干嘛的。获取总高度,这里面肯定有坑,而且不少,我建议亲自尝试。我的坑并不一定是你的坑,你的坑我并不一定遇到,就是这样。

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (heightMeasureSpec == MeasureSpec.EXACTLY) {
            //给width赋值
            //尽量用width:match   height:wrap
        } else {
//            WindowManagerager manager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
//            Display display = manager.getDefaultDisplay();
//            display.getSize(point);
//            totalWidth = point.x;
            // TODO: 2017/5/26 测试这么写 ,正式用上面4行代码
            totalWidth = 720;

            keyboardHeight = (totalWidth - lineWidth >> 1) / 3;
        }
        //getHeaderTotalHeight()+getKeyboardPreLineHeight()<<2
        int measureHeight = (getKeyboardPreLineHeight() << 2) //键盘高度
                + getHeaderTotalHeight()//输入支付密码高度
                +getPasswordTotalHeight()//密码总高度
                +getTextHeight(forgetPasswordPaint)+forgetPasswordMarginBottom;//忘记密码总高度
        Log.d("PasswordView", "===width:" + totalWidth + "   height:" + measureHeight);
        setMeasuredDimension(totalWidth, measureHeight);

    }

那么大头来了,onDraw();这个方法是主要工作内容之一。主要要求熟练掌握 Rect RectF Canvas Paint 的api。Canvas有许多draw方法,如有不熟悉的,自行查询。
分析,功能
这里写图片描述
这是我们的功能界面。
简单分析一下,有哪些绘制点。
1、有一个标题。
2、密码显示区域。
3、忘记密码。
4、键盘输入界面。
对没有太多自定义控件绘制经验的人来说,drawText可能会比较蛋疼,特别是涉及到偏移量的计算,把握不好。
那么你只需要把你要绘制区域的4点坐标封装在Rect /RectF里然后 测量文字高度很容易得出一个正确的偏移量。
比如:你想绘制 Top 高度 0 Bottom 值 200,想要让文居中。
那么文字居中的计算方式是:
Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
top+bottom-fontMetricsInt.bottom-fontMetricsInt.top>>1 的计算结果就是高度的偏移量。
下面就是绘制各种区域:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //绘制顶部View
        drawTopValue(canvas);
        //绘制输入密码框
        drawPasswordLine(canvas);
        //绘制忘记密码
        drawForgetPassword(canvas);
        //绘制键盘
        drawKeyboard(canvas);
    }

其中绘制 键盘逻辑比较重要,我给出我的绘制思路,有更好的欢迎交流

/**
     * 绘制键盘
     */
    private KeyboardBean[] keyboards=new KeyboardBean[12];//存放keyboard数组
    private Rect keyboardTotalArea;//键盘界面总大小
    private void drawKeyboard(Canvas canvas) {
        int startY=forgetRect.bottom+ forgetPasswordMarginBottom;
        //绘制4条横线
        for (int i=0;i<4;i++) {
            int horizontalLineStarY=startY+getKeyboardPreLineHeight()*i;
            canvas.drawLine(0,horizontalLineStarY,totalWidth,horizontalLineStarY+lineWidth,linePaint);
        }
        for (int i=0;i<2;i++) {
            int x_offset =totalWidth/3*(i+1);
            canvas.drawLine(x_offset,startY,x_offset+1,getHeight(),linePaint);
        }
        //将所有keyboard存入数组
        if (keyboards[0] == null) {
            String[] drawValue=new String[]{"1","2","3","4","5","6","7","8","9","","0","<--"};
            int preWidth=totalWidth/3;
            for (int i=0;i<12;i++) {
                int startX=i%3*preWidth;
                int endX=startX+preWidth;
                int y_star=startY+i/3*(keyboardHeight+lineWidth);
                int y_end=y_star+keyboardHeight+lineWidth;
                Rect rect=new Rect(startX,y_star,endX,y_end);
                boolean isFunction=false;
                if (i == 9 | i == 11) {
                    isFunction=true;
                }
                keyboards[i]=new KeyboardBean(isFunction,rect,PaintFactory.createPaint(lineWidth),drawValue[i]);
            }
            keyboardTotalArea=new Rect(0,startY,totalWidth,getHeight());
        }

        //去绘制所有背景
        for (KeyboardBean b:keyboards) {
            Rect rect = b.getRect();
            canvas.drawRect(rect, b.getBackgroundPaint());
            canvas.drawText(
                    b.getDrawText(),
                    rect.centerX()-(measureText(textPaint,b.getDrawText())>>1),
                    getTextOffsetTop(textPaint,rect),
                    textPaint);
        }
    }
 private int measureText(TextPaint paint, String value) {
        return (int) paint.measureText(value);
    }
    /**
     * 获取文本居中偏移量
     */
    private int getTextOffsetTop(TextPaint paint,Rect targetRect) {
        Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
        return targetRect.bottom+targetRect.top-fontMetricsInt.bottom-fontMetricsInt.top>>1;
    }
     /**
     * 键盘管理实体
     */
    private class KeyboardBean {
        //在onDraw的时候会用到是否是按下状态会有不同颜色显示
        private boolean isPressed =false;
        //功能键只有2个 分别为 空白内容 和 删除
        private boolean isFunctionKey=false;
        //记录每一个item的绘制区域
        private Rect rect;
        //画笔
        private Paint paint;
        //绘制文字
        private String drawText;
        //构造

        private KeyboardBean( boolean isFunctionKey, Rect rect, Paint paint,String drawText) {
            this.isFunctionKey = isFunctionKey;
            this.rect = rect;
            this.paint = paint;
            this.drawText=drawText;
        }

        //根据不同性质获取不同背景画笔
        private Paint getBackgroundPaint() {
            //按下状态同意设置为这个颜色
            if (isPressed) {
                paint.setStyle(Paint.Style.FILL);
                paint.setColor(0x6F666666);
            } else {
                //功能型按键设置这个颜色
                if (isFunctionKey) {
                    paint.setStyle(Paint.Style.FILL);
                    paint.setColor(0x6F999999);
                } else {
                    //其他建设置为空白
                    paint.setColor(Color.TRANSPARENT);
                }
            }

            return paint;
        }

        //其他一些set和get方法 不再额外添加注释

        private Rect getRect() {
            return rect;
        }

        public void setPressed(boolean pressed) {
            isPressed = pressed;
        }

        private String getDrawText() {
            return drawText;
        }

        @Override
        public String toString() {
            return "KeyboardBean{" +
                    "isPressed=" + isPressed +
                    ", isFunctionKey=" + isFunctionKey +
                    ", rect=" + rect +
                    ", paint=" + paint +
                    ", drawText='" + drawText + '\'' +
                    '}';
        }
    }

其他的请小伙伴自己根据自己的业务需求去做处理。
其实还有一个重头戏,事件处理。其实不客气的讲,让一个Android开发者自己设计一个OnClickListener的实现,大部分人是实现不了的,有的可能会有一些bug处理不到,当然我的也不一定正确,我没有看过关于onClickListener的实现原理代码,只是凭借我的想法去做这个事情,如果有不同意见,请交流。
先一点一点来。事件主要处理三种 down move 和up
我把这部分所有代码都先放出来

**
     * 分别是 按下动作 x,y值 移动操作x,y值 抬起动作x,y值
     */
    float[] touchData=new float[]{0,0,0,0,0,0};
    //分别是 按下 移动 抬起 如果没有设置-1
    private int defaultIndex=-1;
    int[] keyboardIndex=new int[]{defaultIndex,defaultIndex,defaultIndex};
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //按下
                touchData[0]=event.getX();
                touchData[1]=event.getY();
                onDownEvent();
                break;
            case MotionEvent.ACTION_UP:
                //抬起
                touchData[4]=event.getX();
                touchData[5]=event.getY();

                onUpEvent();
                break;
            case MotionEvent.ACTION_MOVE:
                //移动
                touchData[2]=event.getX();
                touchData[3]=event.getY();

                onMoveEvent();
                break;

            default:
                break;
        }
        return true;
    }
    /**
     * 按下事件响应
     */
    private void onDownEvent() {
        float x=touchData[0];
        float y=touchData[1];
        //判断区域,并且赋值
        action=getAreaFlagByXY(x,y);

//        if (isKeyboardArea(y)) {
//
//        } else if (isForgetPasswordArea(x, y)) {
//
//        }

        if (action == ACTION_KEYBOARD) {
            int index=getIndexByXY(x,y);
            keyboards[index].setPressed(true);
            keyboardIndex[0]=index;
            invalidate();
        }
    }

    /**
     * 抬起动作
     */
    private void onUpEvent() {
        float x=touchData[4];
        float y=touchData[5];
        Log.i("up x y","===x:"+x+"  y"+y);
        //判断键盘区域
        if (isKeyboardArea(y)&&action==ACTION_KEYBOARD) {
            int index=getIndexByXY(x,y);
            //记录抬起下标值
            keyboardIndex[2]=index;
            //按下下标跟抬起下标不相等,置缺省
            if (keyboardIndex[0] != keyboardIndex[2]) {
                keyboardIndex[0] = defaultIndex;

            } else {
                //判断是空白还有删除
                if (index == 9 || index == 11) {
                    if (index == 11) {
                        //如果是删除就删除
                        deletePassword();
                    }//过滤掉是9 没有东西的情况
                } else {
                    //如果不是删除就是增加,以下为增加逻辑
                    if (password.length() < passwordCount) {
                        addPassword(keyboards[index].getDrawText());
                    }

                }
                //设置Key对象为 抬起状态,刷新UI的时候用到
                keyboards[index].setPressed(false);
                //刷新
                invalidate();
            }

        }
        //校验 点击忘记密码区域
        if (isForgetPasswordArea(x, y) && action == ACTION_FORGET_PASSWORD) {
            if (mListener != null) {
                mListener.onForgetPasswordClick();
            }
        }
        //置为缺省值
        action=ACTION_EMPTY_AREA;
    }

    //移动
    private void onMoveEvent() {
        float x=touchData[2];
        float y=touchData[3];
        if (action == ACTION_KEYBOARD) {
           KeyboardBean bean= keyboards[keyboardIndex[0]];
            if (!bean.getRect().contains((int) x, (int) y)) {
                keyboards[keyboardIndex[0]].setPressed(false);
                action=ACTION_EMPTY_AREA;
                invalidate();
            }
        }
    }

主要解决问题:抬起和按下的区域不是有效区域,比如 在键盘1按下,在键盘数字4抬起,那么这个触碰事件是无效的。移动过程中移出有效区域,比如按下是键盘数字1 移动到2了 那么此时,1的背景应该不是点击时候的背景,而是默认背景。在抬起动作相应点击事件。
就先写到这里吧。给同学们一个思路,遇到问题可以尝试着换种解决方式,我可以 放心大胆的讲:效率上,这种方式完爆 10几个控件去组合。虽然现在手机好看不出来。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值