仿支付宝支付键盘

来源:互联网 发布:500px哪些摄影师 知乎 编辑:程序博客网 时间:2024/06/10 10:51

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

由于只是已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几个控件去组合。虽然现在手机好看不出来。。。。。。

原创粉丝点击