安卓/Android 模仿支付宝/微信 支付密码输入框的自定义View

类似这个密码输入框相信在博客上已经不少了,但是我稍微看了一下,大都是通过XML布局,然后组合成的一个自定义View。但是我想只用一个java文件,拿来就用该怎么办呢?

那就需要自己画出来了。水平有限,这里提供我自己自定义的一个过程,哪里不好,大家提提意见。

先上个效果图预览吧,我这里只是实现了那个框,具体输入布局大家可以在xml随意定义。


使用起来还是比较方便的,主要开放有四个方法和一个输入完成的回调接口。

这几个方法下面会介绍。


实现过程


这个密码输入框整体是一个输入框,每个密码之间有一根竖线间隔,每输入一个密码,就绘制一个屏蔽图案(一般是圆形小点)。

那么我们需要:

1.绘制外部的输入框

2.根据密码个数绘制间隔的竖线

3.根据用户输入的密码个数绘制屏蔽图

大概流程就这样,具体每一部分代码就不贴出来了,我直接贴这个文件的代码吧

package cn.small_qi.transitiontest.diyview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by small_qi on 2017/7/12.
 */

public class PayInputView extends View {

    private Paint mPaint;
    private InputFinishListener inputFinishListener;//输入完成监听

    private int inputNum=0;//当前输入的密码个数
    private int passwordNum=6;//密码个数

    private int boundWidth=2;//外层框线条粗细
    private int boundColor= Color.BLACK;//外层框线条颜色
    private int boundRadius=0;//外框圆角半径

    private int deliverWidth=1;//分割线粗细
    private int deliverColor=Color.GRAY;//分割线条颜色
    private int deliverPadding=5;//分割线距离框的大小

    private int circleRadius =15;//密码圆点半径大小
    private int circleColor= Color.BLACK;//密码圆点颜色

    private StringBuilder currentPassword;//用户输入的密码

    public PayInputView(Context context) {
        super(context);
        init();
    }

    public PayInputView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        currentPassword=new StringBuilder();
        mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
    }

    public PayInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //TODO 此处应处理控件的测量逻辑
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width =getWidth();
        int height =getHeight();
        float deliverSize=(width-getPaddingLeft()-getPaddingRight())/passwordNum;
        //1.画外框
        drawBound(canvas, width, height);
        //2.画分割线
        drawDeliver(canvas, height, deliverSize);
        //3.输入密码之后显示的图案
        drawCircle(canvas, height, deliverSize);
    }

    private void drawBound(Canvas canvas, int width, int height) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(boundWidth);
        mPaint.setColor(boundColor);
        RectF rectF = new RectF(getPaddingLeft(),getPaddingTop(),width-getPaddingRight(),height-getPaddingBottom());
        canvas.drawRoundRect(rectF,boundRadius,boundRadius,mPaint);
    }

    private void drawDeliver(Canvas canvas, int height, float deliverSize) {
        mPaint.setStrokeWidth(deliverWidth);
        mPaint.setColor(deliverColor);
        Path path = new Path();
        for (int i = 1; i < passwordNum; i++) {
            path.reset();
            path.moveTo(deliverSize*i+getPaddingLeft(),0+deliverPadding+getPaddingTop());
            path.lineTo(deliverSize*i+getPaddingLeft(),height-deliverPadding-getPaddingBottom());
            canvas.drawPath(path,mPaint);
        }
    }

    private void drawCircle(Canvas canvas, int height, float deliverSize) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(circleColor);
        for (int i = 0; i < inputNum; i++) {
            canvas.drawCircle(deliverSize*i+getPaddingLeft()+deliverSize/2,(height-getPaddingTop()-getPaddingBottom())/2+getPaddingTop(), circleRadius,mPaint);
        }
    }

    //输入一个密码
    public void inputPassword(Object pwd){
        if (inputNum<passwordNum) {
            currentPassword.append(pwd);
            inputNum++;
        }
        invalidate();
        if (inputNum==passwordNum){
            if (inputFinishListener!=null){
                inputFinishListener.onFinish(getPassword());
            }
        }
    }
    //删除一个密码
    public void deletePassword(){
        if (currentPassword.length()>0) {
            currentPassword.deleteCharAt(currentPassword.length() - 1);
            inputNum--;
        }
        invalidate();
    }
    //清空输入的所有密码
    public void cleanInput(){
        inputNum=0;
        currentPassword.delete(0,currentPassword.length());
        invalidate();
    }
    //获取输入的所有密码
    public String getPassword(){
        return currentPassword.toString();
    }

    public void setInputFinishListener(InputFinishListener inputFinishListener) {
        this.inputFinishListener = inputFinishListener;
    }

    public interface InputFinishListener{
        void onFinish(String pwd);
    }
}

之前说的开放四个方法分别就是:输入一个密码、删除一个密码、清空输入、获取当前输入的所有密码这四个。监听就是用户输入所有密码后回调。


总体来说,能够自定义的属性还是很多的,但是我没有开放出方法,也没有定义xml属性,如果需要,大家稍作修改即可。


大家看我的全局变量的定义就知道能够自动义那些内容,包括最大输入的密码个数;外框线条大小、颜色、圆角,分割线大小、颜色、距离外框的大小、屏蔽图案的大小颜色等。

如果有需要,大家还可以修改分割线是实线还是虚线、屏蔽图案任意图片、各种阴影效果,输入光标等都可以的。

使用起来也比较方便,只需要在xml中定义:

    <your.package.PayInputView
        android:id="@+id/inputview"
        android:layout_width="256dp"
        android:padding="1dp"
        android:layout_height="48dp" />

这里有两点需要注意的是,

1.为什么我加了一个padding,因为我绘制并没有留出空隙,这个view是多大就画多大,如果大家觉得边框过于紧凑,加个padding就行了(建议加上,否则如果设置外框线条太粗可能显示没有设置的粗,或者圆角不明显等)。

2.我直接定义了宽和高,这显然不太好,其实这只是为了方便我测试而已,大家把onMeasure方法测量逻辑改成下面这样,使用wrap_content就行咯。


使用wrap_content:

 /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    private int dip2px(float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode=MeasureSpec.getMode(widthMeasureSpec);
        int widthSize=MeasureSpec.getSize(widthMeasureSpec);
        int heightMode=MeasureSpec.getMode(heightMeasureSpec);
        int heightSize=MeasureSpec.getSize(heightMeasureSpec);
        int width,height;
        if (widthMode==MeasureSpec.AT_MOST){
            width=dip2px(36)*passwordNum;
        }else {
            width=widthSize;
        }
        if (heightMode==MeasureSpec.AT_MOST){
            height=dip2px(36);
        }else{
            height=heightSize;
        }
        setMeasuredDimension(width,height);
    }



java代码中的使用:

 private PayInputView payView;
    private void initPayView() {
        payView = (PayInputView) findViewById(R.id.inputview);
        //输入完成监听
        payView.setInputFinishListener(new PayInputView.InputFinishListener() {
            @Override
            public void onFinish(String pwd) {
                Toast.makeText(MainActivity.this, "你输入的密码:"+pwd, Toast.LENGTH_SHORT).show();
            }
        });
    }
    public void onClick(View v){
        switch (v.getId()){
            case R.id.input://输入按钮
                //这里只是模拟用户输入密码,具体使用时,用户按一个数字就调用这个方法把密码传过去即可。
                payView.inputPassword(2);
                break;
            case R.id.delete://删除按钮
                payView.deletePassword();
                break;
            case R.id.clear://清除按钮
                payView.cleanInput();
                break;
        }
    }


这大概只能算是半成品,但是如果要求不高,拿来直接用也是可以的。

如果有特殊要求,例如我不想自己写个密码输入布局,我想使用自带输入法,点击密码框就弹出输入法输入。这就要大家自己实现了监听输入法的输入了。


----------------修改继承自edittext 可以调用自带输入法进行密码输入,同时edittext的大部分属性都可以使用。同时加了输入指示,输入完成变色等---------

效果图:


JAVA源码:

package cn.small_qi.transitiontest.diyview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v7.widget.AppCompatEditText;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;

/**
 * Created by small_qi on 2017/7/12.
 */

public class PayInputView extends AppCompatEditText {

    private Paint mPaint;
    private InputFinishListener inputFinishListener;//输入完成监听

    private int inputNum = 0;//当前输入的密码个数
    private int passwordNum = 6;//密码个数

    private int boundWidth = 2;//外层框线条粗细
    private int boundColor = Color.BLACK;//外层框线条颜色
    private int boundRadius = 0;//外框圆角半径

    private int deliverWidth = 1;//分割线粗细
    private int deliverColor = Color.GRAY;//分割线条颜色
    private int deliverPadding = 5;//分割线距离框的大小

    private int circleRadius = 15;//密码圆点半径大小
    private int circleColor = Color.BLACK;//密码圆点颜色

    private StringBuilder currentPassword;//用户输入的密码

    public PayInputView(Context context) {
        super(context);
        init();
    }

    public PayInputView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        currentPassword = new StringBuilder();
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        setBackgroundDrawable(null);
        setMaxLines(1);
        //禁止复制等操作
        disableCopy();

    }

    private void disableCopy() {
        setLongClickable(false);
        setTextIsSelectable(false);
        setSelected(false);
        setCustomSelectionActionModeCallback(new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                return false;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                return false;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                return false;
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {

            }
        });
    }

    public PayInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    private int dip2px(float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width, height;
        if (widthMode == MeasureSpec.AT_MOST) {
            width = dip2px(36) * passwordNum;
        } else {
            width = widthSize;
        }
        if (heightMode == MeasureSpec.AT_MOST) {
            height = dip2px(36);
        } else {
            height = heightSize;
        }
        setMeasuredDimension(width, height);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        if (inputNum==passwordNum){
            //如果输入完毕 整个框变色  如无此需求可删除这段代码
            if (hasFocus()&&hasFocusable()){
                boundColor=Color.BLUE;
                deliverColor =Color.BLUE;
            }else{
                boundColor=Color.BLACK;
                deliverColor =Color.BLACK;
            }
        }else{
            boundColor=Color.BLACK;
            deliverColor =Color.BLACK;
        }

        //
        int width = getWidth();
        int height = getHeight();
        float deliverSize = (width - getPaddingLeft() - getPaddingRight()) / passwordNum;
        //1.画外框
        drawBound(canvas, width, height);
        //2.画分割线
        drawDeliver(canvas, height, deliverSize);
        //2.1.输入指示
        drawIndicator(canvas, height, deliverSize);
        //3.输入密码之后显示的图案
        drawCircle(canvas, height, deliverSize);
    }
    private void drawIndicator(Canvas canvas, int height, float deliverSize) {
        if (inputNum < 0 || inputNum == passwordNum ||!(hasFocus()&&hasFocusable())) return;
        mPaint.setColor(Color.BLUE);
        //mPaint.setShadowLayer(5,3,3,0xFFFFAAFF);//阴影效果
        mPaint.setStrokeWidth(boundWidth + 2);
        RectF rectF = new RectF(deliverSize * inputNum + getPaddingLeft(), getPaddingTop(), deliverSize * (inputNum + 1) + getPaddingLeft(), height - getPaddingBottom());
        canvas.drawRoundRect(rectF, boundRadius, boundRadius, mPaint);
    }
    private void drawBound(Canvas canvas, int width, int height) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(boundWidth);
        mPaint.setColor(boundColor);
        RectF rectF = new RectF(getPaddingLeft(), getPaddingTop(), width - getPaddingRight(), height - getPaddingBottom());
        canvas.drawRoundRect(rectF, boundRadius, boundRadius, mPaint);
    }
    private void drawDeliver(Canvas canvas, int height, float deliverSize) {
        mPaint.setStrokeWidth(deliverWidth);
        mPaint.setColor(deliverColor);
        Path path = new Path();
        for (int i = 1; i < passwordNum; i++) {
            path.reset();
            path.moveTo(deliverSize * i + getPaddingLeft(), 0 + deliverPadding + getPaddingTop());
            path.lineTo(deliverSize * i + getPaddingLeft(), height - deliverPadding - getPaddingBottom());
            canvas.drawPath(path, mPaint);
        }
    }
    private void drawCircle(Canvas canvas, int height, float deliverSize) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(circleColor);
        for (int i = 0; i < inputNum; i++) {
            canvas.drawCircle(deliverSize * i + getPaddingLeft() + deliverSize / 2, (height - getPaddingTop() - getPaddingBottom()) / 2 + getPaddingTop(), circleRadius, mPaint);
        }
    }
    //监听文字输入长度
    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        Log.i("TAG", "onTextChanged: " + text + " " + start + " " + lengthBefore + " " + lengthAfter);
        //改变之后长度大于之前 证明是输入操作
        if (Math.abs(lengthAfter - lengthBefore) == 1) {
            if (lengthAfter < lengthBefore) {
                deletePassword();
            } else if (inputNum<passwordNum){
                if (start == 0) {
                    notifyPasswordChange(text.charAt(lengthAfter - 1));
                } else {
                    notifyPasswordChange(text.charAt(start));
                }
            }
        } else{
            for (int i = 0; i < lengthAfter-lengthBefore; i++) {
                if (inputNum<passwordNum) {
                    notifyPasswordChange(text.charAt(start + i));
                }
            }
        }
        //缓存字符大于20时清空文字缓存
        if (text.length()>20){
            setText(getPassword());
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

    //输入一个密码
    public void inputPassword(Object pwd) {
        append(String.valueOf(pwd));
    }

    private void notifyPasswordChange(Object pwd) {
        if (inputNum < passwordNum) {
            currentPassword.append(pwd);
            inputNum++;
            invalidate();
        }
        if (inputNum == passwordNum) {
            if (inputFinishListener != null) {
                inputFinishListener.onFinish(getPassword());
            }
        }
    }

    //删除一个密码
    public void deletePassword() {
        if (currentPassword.length() > 0) {
            currentPassword.deleteCharAt(currentPassword.length() - 1);
            inputNum--;
            invalidate();
        }
    }
    //清空输入的所有密码
    public void cleanInput() {
        inputNum = 0;
        currentPassword.delete(0, currentPassword.length());
        invalidate();
    }
    //获取输入的所有密码
    public String getPassword() {
        return currentPassword.toString();
    }
    public void setInputFinishListener(InputFinishListener inputFinishListener) {
        this.inputFinishListener = inputFinishListener;
    }
    public interface InputFinishListener {
        void onFinish(String pwd);
    }
}



--有意见别忘了在评论中提出哦--

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值