Android高效率实现自定义密码输入效果,仿微信和支付宝密码输入效果

转载请注明出处:  http://blog.csdn.net/jakeyangchina/article/details/53338444

今天使用支付宝支付时突然发现支付页面输入密码效果感觉很棒,又打开微信支付页面发现效果也类似,于是乎我利用星期日休息时间仿照效果也做了一个Demo,废话不多说了直接上效果图

效果图如下:

这里写图片描述

简单概要:

实现带动画效果,当输入长度增加时,圆变大,当输入长度减少时,圆变小,当输入完后自动会回调方法给调用者。密码输入长度通过属性设置android:maxLength=”6”

注意事项:

  1. 自定义MyEditText类继承EditText实现Animation.AnimationListener接口
  2. 代码中通过反射获取maxLength
  3. 写类MyAnimation继承Animation类获取百分比值实现动画效果
  4. 写回调接口

遇到的问题:

  1. 当输入长度增加,再此减少长度,再次增加长度后会发现会自动增加两个圆,减少也是,产生原因:获取当前长度有误,解决办法:在判断减少减少状态时,把获取到的当前长度+1
  2. 当输入完会执行多次回调方法,产生原因:快速输入内容到最长度,因为动画没执行完,当动画执行完会多执行一次回调,解决办法:使用标识符判断

接下来开始上代码分析:

首先布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:padding="5dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.jakeyang.testview.MainActivity">

    <com.jakeyang.testview.MyEditText
        android:layout_marginTop="20dp"
        android:id="@+id/MyEditText"
        android:inputType="numberPassword"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:maxLength="6" />
    <TextView
        android:textSize="29sp"
        android:text="输入内容为:"
        android:id="@+id/TextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:textSize="29sp"
        android:id="@+id/TextView_complete"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

这里必须设置android:maxLength=”6”

自定义MyEditText类继承EditText
   /**
     * 画线
     */
    private Paint linePaint;

    /**
     * 画圆
     */
    private Paint cPaint;

    /**
     * 控件宽度
     */
    private int width;

    /**
     * 控件高度
     */
    private int height;

    /**
     * 绘制框内填充色
     */
    private Paint kPaintColor;

    /**
     * 记录框内颜色矩形大小
     */
    private RectF krectF;

    /**
     * 记录外轮框矩形大小
     */
    private RectF rectF;

    /**
     * 获取最大可以输入的长度
     */
    private int maxLength;

    /**
     * 给边距值,因为线粗
     */
    private float padding = 1f;

    /**
     * 四周倒角半径
     */
    private float chamferRadius = 16f;

    /**
     * 圆半径
     */
    private float radius = 22;

    /**
     * 百分比,用于计算圆的大小
     */
    private float percent = 0f;

    /**
     * 控制框内背景色
     */
    private String backgroundColor = "#eeeeee";

    /**
     * 动画类
     */
    private MyAnimation animationy;
    /**
     * 运行一次
     */
    private boolean isFirst = false;

    /**
     * 标识使回调方法执行一次,当执行圆减少时恢复可执行回调方法
     */
    private boolean isFirstComplete = false;

    /**
     * 用于记录圆个数是增加还是减少
     */
    private boolean isAdd = true;

    /**
     * 记录上次输入内容的长度
     */
    private int length = 0;

    /**
     * 记录当前输入内容的长度
     */
    private int currentLength = 0;

    /**
     * 记录当前输入的内容
     */
    private CharSequence texts;

上面属性标注很明细了,就不再解说了
接下来通过反射获取系统中mMax字段值

    /**
     * 获取EditText输入框内允许输入的最大个数,通过属性必须设置值
     * @return
     */
    private int getMaxLength() {
        //系统提供的方法,查看源码可知字段mMax保存在LengthFilter类内,LengthFilter类是InputFilter内部实现类
        InputFilter[] filters = getFilters();
        for(InputFilter filter : filters) {
            if("android.text.InputFilter.LengthFilter".equals(filter.getClass().getCanonicalName())) {
                try {
                    Field mMax = filter.getClass().getDeclaredField("mMax");
                    mMax.setAccessible(true);
                    //获取字段值
                    int num = (int)mMax.get(filter);
                    return num;
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
        return -1;
    }

通过查看源码可知,InputFilter是接口,在InputFilter接口内部有LengthFilter类,LengthFilter是InputFilter实现类,在LengthFilter类中定义字段mMax,通过反射得到值


    public MyEditText(Context context) {
        this(context,null);
    }

    public MyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public MyEditText(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

上面是构造函数,其中init()方法是初始化函数具体代码如下

    /**
     * 初始化数据
     */
    private void init() {
        //隐藏光标
        setCursorVisible(false);
        //获取焦点
        requestFocus();
        setFocusableInTouchMode(true);
        setFocusable(true);

        //绘制外边边框和中间线条
        linePaint = new Paint();
        //设置抗锯齿
        linePaint.setAntiAlias(true);
        //设置样式
        linePaint.setStyle(Paint.Style.STROKE);

        //绘制框内填充色
        kPaintColor = new Paint();
        kPaintColor.setAntiAlias(true);
        //设置框内颜色
        kPaintColor.setColor(Color.parseColor(backgroundColor));
        //设置填充
        kPaintColor.setStyle(Paint.Style.FILL);

        //绘制圆
        cPaint = new Paint();
        cPaint.setStyle(Paint.Style.FILL);
        cPaint.setAntiAlias(true);

        //创建动画
        animationy = new MyAnimation();
        animationy.setDuration(200);
        animationy.setAnimationListener(this);
    }

上面标注很明细了,可以直接看标准,不详细解释
接下来覆写onLayout方法,在其中获取控件宽高等

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        //让代码执行一次
        if(!isFirst && changed) {
            //记录控件当前宽度
            width = getWidth();
            //记录控件当前高度
            height = getHeight();
            //绘制框内颜色
            krectF = new RectF(padding,padding,width-padding,height-padding);
            //绘制外轮廓
            rectF = new RectF(padding,padding,width-padding,height-padding);
            maxLength = getMaxLength();
            //只允许执行一次
            isFirst = true;
        }
    }

接下来覆写onTextChanged方法进行设置标识符确认圆是增加还是减少状态

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        //记录内容,用于传给回调函数
        texts = text;
        //获取当前文本输入的长度
        currentLength = text.length();
        if(currentLength >= length) {
            //增加圆
            isAdd = true;
        }else {
            //减少圆
            isAdd = false;
            //当减少圆时,恢复完成时的回调
            isFirstComplete = false;
            //防止当为减少时,圆瞬间减少两个
            currentLength = currentLength + 1;
        }

        //回调函数,给调用者
        if(listener != null) {
            if(currentLength == 1 && !isAdd) {
                //防止输入框内没有值时,调用者仍可获取到第一个输入值
                listener.onTextChanged("");
            }else {
                listener.onTextChanged(text);
            }
        }

        if(currentLength <= getMaxLength()) {
            if(animationy != null) {
                //每次进来清除上次动画
                clearAnimation();
                //开启新动画
                startAnimation(animationy);
            }else {
                invalidate();
            }
        }

        //记录上次输入内容的长度
        length = currentLength;

    }

当内容改变时系统调用onTextChanged方法,用length记录上次长度,用当前currentLength长度和上次长度进行比较判断,如果当前长度大于上次长度说明圆增加,把isAdd标识设置为true,否则反之,这里需要注意的是当圆为减少时,此时需要把当前长度+1,要不然绘制圆时会出现同时增加两个圆或者减少两个圆现象,原因是在绘制圆时for循环中当前长度少一个圆引起的,详细见for循环

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制框内颜色
        canvas.drawRoundRect(krectF,chamferRadius,chamferRadius,kPaintColor);
        //绘制外轮廓
        canvas.drawRoundRect(rectF,chamferRadius,chamferRadius,linePaint);
        //计算每个输入框宽
        float x = width / maxLength;
        //计算每个输入框高
        float y = height;
        //画线
        for(int i = 1; i < maxLength; i++) {
            //画框内竖线
            canvas.drawLine(x*i,padding,x*i,y - padding,linePaint);
        }

        for(int i = 0; i < currentLength; i++) {

            if(isAdd) {
                //增加圆isAdd = true;
                if(i < currentLength-1) {
                    //画圆,不为当前圆时,不需要产生动画效果
                    canvas.drawCircle(x/2+x*i,y/2,radius,cPaint);
                }else if(i == currentLength-1) {
                    //画圆,当画当前圆时,需要产生扩大效果
                    canvas.drawCircle(x/2+x*i,y/2,radius*percent,cPaint);
                }
            }else {
                //减少圆isAdd = false;
                if(i < currentLength - 1) {
                    //画圆,不为当前圆时,不需要产生动画效果
                    canvas.drawCircle(x/2+x*i,y/2,radius,cPaint);
                }else if(i == currentLength - 1) {
                    //画圆,当画当前圆时,需要产生缩小效果
                    canvas.drawCircle(x/2+x*i,y/2,radius-radius*percent,cPaint);
                }
            }
        }
    }

上面使用两个for循环,第一个for循环绘制竖线,第二个for循环是绘制圆,在for循环中通过判断当前isAdd是增加圆还是减少圆来进行绘制,需要注意是减少圆时的个数

   /**
     * 动画开始时,回调此方法
     * @param animation
     */
    @Override
    public void onAnimationStart(Animation animation) {

    }

    /**
     * 动画结束时回调次方法
     * @param animation
     */
    @Override
    public void onAnimationEnd(Animation animation) {
        //动画完成执行
        if(isAdd && currentLength == getMaxLength() && listener != null && !isFirstComplete) {
            //最后一个圆执行完动画后,执行此代码,为了保证只允许执行一次,添加个isFirstComplete标识用于判断
            isFirstComplete = true;
            listener.onComplete(texts);
        }
    }

    /**
     * 动画重复执行时,回调此方法
     * @param animation
     */
    @Override
    public void onAnimationRepeat(Animation animation) {

    }

上边方法为实现AnimationListener接口时需要实现的方法,其中需要注意的是在动画结束后去执行自定义回调方法,防止动画没执行完就执行回调

    /**
     * 执行动画
     */
    private class MyAnimation extends Animation {
        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            //记录百分比值
            percent = interpolatedTime;
            //百分比变化,需要更新页面
            postInvalidate();
        }
    }

上边类为继承动画类,主要是为了得到applyTransformation方法,参数interpolatedTime是系统传回来的运动距离的百分值
接下来自定义完成时的接口

 public EditTextContentListener listener;

    /**
     * 设置输入内容监听接口
     * @param listener
     */
    public void setOnEditTextContentListener(EditTextContentListener listener) {
        this.listener = listener;
    }

    /**
     * 输入内容监听接口,提供给调用者
     */
    public interface EditTextContentListener {
        /**
         * 完成时,回调此方法
         * @param text
         */
        public void onComplete(CharSequence text);

        /**
         * 输入框内容改变时,回调次方法
         * @param text
         */
        public void onTextChanged(CharSequence text);
    }

上面注解很详细,接下来Activity类

public class MainActivity extends AppCompatActivity {

    private MyEditText myEditText;
    private TextView textView;
    private TextView textViewComplete;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.TextView);
        textViewComplete = (TextView) findViewById(R.id.TextView_complete);
        myEditText = (MyEditText) findViewById(R.id.MyEditText);
        myEditText.setOnEditTextContentListener(new MyEditText.EditTextContentListener() {
            @Override
            public void onComplete(CharSequence text) {
                if(text != null) {
                    textViewComplete.setText("输入完成,内容为:"+text);
                }
            }

            @Override
            public void onTextChanged(CharSequence text) {
                if(text != null) {
                    textViewComplete.setText("");
                    textView.setText("输入内容为:"+text);
                }
            }
        });
    }

如果大家还有什么疑问,请在下方留言。

如何有不足的地方希望大家指出来,共同学习。
源码下载,请点击这里!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值