Android:仿支付寶交易密碼框

App需要支付場(chǎng)景的時(shí)候畸颅,都會(huì)讓用戶輸入密碼交易框,如果用系統(tǒng)或者第三方鍵盤(pán)可能導(dǎo)致密碼泄露方援。因此没炒,比較多的App會(huì)自定義密碼輸入鍵盤(pán)來(lái)提供安全性。本文依照支付寶密碼輸入界面來(lái)設(shè)計(jì)犯戏,同時(shí)提供隨機(jī)鍵盤(pán)功能送火。
項(xiàng)目地址:Github

看效果圖

密碼輸入框效果圖

模擬器上,會(huì)導(dǎo)致線條繪制模糊先匪。真機(jī)運(yùn)行效果更棒种吸。

數(shù)字密碼鍵盤(pán)安全級(jí)別

低:系統(tǒng)提供鍵盤(pán)或者第三方輸入法鍵盤(pán)。
中:自定義鍵盤(pán)呀非,但不隨機(jī)鍵盤(pán)數(shù)字坚俗。
高:自定義鍵盤(pán)镜盯,且隨機(jī)鍵盤(pán)數(shù)字。

數(shù)字密碼鍵盤(pán)實(shí)現(xiàn)原理

鍵盤(pán)實(shí)現(xiàn)的原理大致有兩種方法猖败。
1.利用系統(tǒng)提供的KeyboardView來(lái)完成速缆。
2.另外一種就是自定義View來(lái)完成鍵盤(pán)繪制。
本文采取的自定義View方式繪制鍵盤(pán)恩闻。
因?yàn)閿?shù)字鍵盤(pán)是九宮格的排列艺糜,所以首先考慮使用GridLayout。
因?yàn)镚ridLayout并不提供均勻分布的功能幢尚,所以我們得重新填充Child View破停。
解決思路如下:
1.填充鍵盤(pán)Key。
2.繪制鍵盤(pán)之間的分割線尉剩。
3.完成鍵盤(pán)Key點(diǎn)擊事件監(jiān)聽(tīng)真慢。
4.輸出鍵盤(pán)密碼的輸入。

看鍵盤(pán)源碼

public class PasswordKeyboard extends GridLayout implements View.OnClickListener, View.OnTouchListener {

    public static final String DEL = "刪除";

    public static final String DONE = "OK";
    //因?yàn)閁ED是給的是iPhone設(shè)計(jì)稿,所以是按照等比的思想設(shè)置鍵盤(pán)Key的高度和寬度
    private static final int IPHONE = 779;
    //每個(gè)鍵盤(pán)Key的寬度,為屏幕寬度的三分之一
    private int keyWidth = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth() / 3;
    //每個(gè)鍵盤(pán)Key的高度
    private int keyHeight = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getHeight() * 59 / IPHONE;

    private int screenWidth = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getWidth();

    private Paint mPaint;
    //List集合存儲(chǔ)Key,方便每次輸錯(cuò)都能再次隨機(jī)數(shù)字鍵盤(pán)
    private final List<Button> keyButtons = new ArrayList<>();

    private WorkHandler mWorkHandler;

    private static final int DELETE = 1;
    //WorkHandler 用于處理長(zhǎng)按"刪除"Key時(shí),執(zhí)行重復(fù)刪除操作边涕。
    private static class WorkHandler extends Handler {

        private int index = 0;

        int diffTime = 100;

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case DELETE:
                    PasswordKeyboard numberKeyBoard = (PasswordKeyboard) msg.obj;
                    numberKeyBoard.handlerClick(DEL);
                    removeMessages(DELETE);
                    Message message = obtainMessage(DELETE);
                    message.obj = numberKeyBoard;
                    if (diffTime > 40) {
                        diffTime = diffTime - index;
                    }
                    sendMessageDelayed(message, diffTime);
                    index++;
                    break;
            }
        }

        public void reset() {
            index = 0;
            diffTime = 100;
        }
    }

    public PasswordKeyboard(Context context) {
        super(context);
        initView();
    }

    public PasswordKeyboard(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public PasswordKeyboard(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        super.onMeasure(widthSpec, heightSpec);
        setMeasuredDimension(screenWidth, keyHeight * 4);
    }

    //重新設(shè)置鍵盤(pán)key位置
    public void resetKeyboard() {
        List<String> keyList = randomKeys(10);
        for (int i = 0; i < keyList.size(); i++) {
            keyButtons.get(i).setText(keyList.get(i));
            keyButtons.get(i).setTag(keyList.get(i));
        }
    }

    private void initView() {
        //必須設(shè)置調(diào)用該方法,不然onDraw方法不執(zhí)行晤碘。如果ViewGroup沒(méi)有背景,則其onDraw方法不執(zhí)行
        setWillNotDraw(false);
        if (getChildCount() > 0) {
            keyButtons.clear();
            removeAllViews();
        }
        //獲取隨機(jī)鍵盤(pán)數(shù)字的字符串
        List<String> keyList = randomKeys(10);
        //填充鍵盤(pán)Key,用Button來(lái)完成Key功能
        for (int i = 0; i < keyList.size(); i++) {
            Button item = new Button(getContext());
            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(keyWidth, keyHeight);
            item.setLayoutParams(params);
            item.setOnClickListener(this);
            item.setText(keyList.get(i));
            item.setBackgroundDrawable(getResources().getDrawable(R.drawable.key_selector));
            //監(jiān)聽(tīng)"刪除"的長(zhǎng)按監(jiān)聽(tīng)事件,完成重復(fù)刪除操作
            if (DEL.equals(keyList.get(i))) {
                item.setOnTouchListener(this);
            }
            item.setTag(keyList.get(i));
            addView(item);
            keyButtons.add(item);
        }
        if (mPaint == null) {
            mPaint = new Paint();
            mPaint.setColor(Color.parseColor("#cccccc"));
            mPaint.setStrokeWidth(1);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //繪制分割線
        canvas.drawLine(0, getMeasuredHeight() / 4, getMeasuredWidth(), getMeasuredHeight() / 4, mPaint);
        canvas.drawLine(0, 2 * getMeasuredHeight() / 4, getMeasuredWidth(), 2 * getMeasuredHeight() / 4, mPaint);
        canvas.drawLine(0, 3 * getMeasuredHeight() / 4, getMeasuredWidth(), 3 * getMeasuredHeight() / 4, mPaint);
        canvas.drawLine(getMeasuredWidth() / 3, 0, getMeasuredWidth() / 3, getMeasuredHeight(), mPaint);
        canvas.drawLine(2 * getMeasuredWidth() / 3, 0, 2 * getMeasuredWidth() / 3, getMeasuredHeight(), mPaint);
    }

    @Override
    public void onClick(View v) {
        String character = v.getTag().toString();
        handlerClick(character);
    }

    private void handlerClick(String character) {
        //密碼字符輸出回調(diào)
        if (mListener != null) {
            if (DONE.equals(character)) {
                mListener.onInput(DONE);
            } else if (DEL.equals(character)) {
                mListener.onInput(DEL);
            } else {
                mListener.onInput(character);
            }
        }
    }

    //生產(chǎn)鍵盤(pán)Key隨機(jī)數(shù)字
    private List<String> randomKeys(int no) {
        int[] keys = new int[no];
        for (int i = 0; i < no; i++) {
            keys[i] = i;
        }
        Random random = new Random();
        for (int i = 0; i < no; i++) {
            int p = random.nextInt(no);
            int tmp = keys[i];
            keys[i] = keys[p];
            keys[p] = tmp;
        }
        List<String> keyList = new ArrayList<>();
        for (int key : keys) {
            keyList.add(String.valueOf(key));
        }
        //將空字符串插入到第10個(gè)位置,是個(gè)無(wú)操作的Key
        keyList.add(9, "");
        //將刪除字符串插入最后
        keyList.add(DEL);
        return keyList;
    }

    public void setOnPasswordInputListener(OnPasswordInputListener listener) {
        this.mListener = listener;
    }

    private OnPasswordInputListener mListener;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (mWorkHandler == null) {
            mWorkHandler = new WorkHandler();
        }
        if (MotionEvent.ACTION_DOWN == event.getAction()) {
            Message msg = mWorkHandler.obtainMessage(DELETE);
            msg.obj = this;
            mWorkHandler.sendMessageDelayed(msg, 500);
        } else if (MotionEvent.ACTION_UP == event.getAction()) {
            mWorkHandler.removeMessages(DELETE);
            mWorkHandler.reset();
        } else if (MotionEvent.ACTION_CANCEL == event.getAction()) {
            mWorkHandler.removeMessages(DELETE);
            mWorkHandler.reset();
        } else if (MotionEvent.ACTION_MOVE == event.getAction()) {

        } else {
            //do nothing
        }
        return false;
    }

    public interface OnPasswordInputListener {
        void onInput(String number);
    }
}

密碼顯示框

因?yàn)椴荒苊魑娘@示輸入,所以我們用“●”代替每位密碼功蜓。自定義密碼顯示框比較簡(jiǎn)單园爷,直接采取繼承View方式完成。
解決思路如下式撼。
1.支持密碼位數(shù)設(shè)置童社。
2.繪制邊框和分割線。
3.繪制“●”著隆。
4.每次密碼輸入改變扰楼,重新繪制“●”。

看源碼

public class PasswordView extends View {

    private int passwordCount;

    private int strokeColor;

    private Paint mCirclePaint;

    private Paint mPaint;

    private int symbolColor;

    private float mRadius;

    private float inputBoxStroke;

    private StringBuffer mText;

    public PasswordView(Context context) {
        super(context);
    }

    public PasswordView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.inputBox);
        //支持某些屬性設(shè)置,比如密碼位數(shù),邊框顏色美浦、寬度,"●"的顏色弦赖、大小
        passwordCount = ta.getInteger(R.styleable.inputBox_passwordCount, 6);
        strokeColor = ta.getColor(R.styleable.inputBox_stokeColor, Color.GRAY);
        symbolColor = ta.getColor(R.styleable.inputBox_symbolColor, Color.BLACK);
        mRadius = ta.getDimension(R.styleable.inputBox_symbolRadius, 12);
        inputBoxStroke = ta.getDimension(R.styleable.inputBox_inputBoxStroke, 1f);
        //設(shè)置輸入框圓角邊框
        GradientDrawable gd = new GradientDrawable();
        gd.setColor(Color.WHITE);
        gd.setStroke((int) inputBoxStroke, strokeColor);
        gd.setCornerRadius(8);
        setBackgroundDrawable(gd);
        ta.recycle();
        if (mPaint == null) {
            mPaint = new Paint();
            mPaint.setColor(strokeColor);
            mPaint.setStrokeWidth(inputBoxStroke);
        }
        if (mCirclePaint == null) {
            mCirclePaint = new Paint();
            mCirclePaint.setColor(symbolColor);
            mCirclePaint.setStyle(Paint.Style.FILL);
        }
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int singleWidth = getMeasuredWidth() / passwordCount;
        int height = getMeasuredHeight();
        //繪制每個(gè)"●"之間的分割線
        for (int i = 1; i < passwordCount; i++) {
            canvas.drawLine(singleWidth * i, 0, singleWidth * i, height, mPaint);
        }
        if (mText != null) {
            //繪制"●"
            int textSize = mText.length() > passwordCount ? passwordCount : mText.length();
            for (int i = 1; i <= textSize; i++) {
                canvas.drawCircle(singleWidth * i - singleWidth / 2, height / 2, mRadius, mCirclePaint);
            }
        }
    }

    public int getPasswordCount() {
        return passwordCount;
    }
    //支持密碼位數(shù)設(shè)置
    public void setPasswordCount(int passwordCount) {
        this.passwordCount = passwordCount;
    }
    //密碼改變,重新繪制
    public void setPassword(CharSequence text) {
        mText = (StringBuffer) text;
        if (text.length() > passwordCount) {
            mText.delete(mText.length() - 1, mText.length());
            return;
        }
        postInvalidate();
    }

    public void clearPassword() {
        if (mText != null) {
            mText.delete(0, mText.length());
        }
    }

    public CharSequence getPassword() {
        return mText;
    }
}

進(jìn)度條繪制

支付寶的驗(yàn)證密碼過(guò)程的進(jìn)度條是采取material design風(fēng)格,不過(guò)做了特殊動(dòng)畫(huà)效果浦辨,當(dāng)驗(yàn)證密碼正確的時(shí)候蹬竖,出現(xiàn)打“??”的動(dòng)畫(huà)。需解決問(wèn)題如下流酬。
1.material design進(jìn)度條旋轉(zhuǎn)的動(dòng)畫(huà)币厕。
2.停止旋轉(zhuǎn)動(dòng)畫(huà),開(kāi)始打“??”動(dòng)畫(huà)芽腾。
3.打“??”動(dòng)畫(huà)完成的回調(diào)旦装。

看源碼

進(jìn)度條旋轉(zhuǎn)動(dòng)畫(huà)過(guò)程,是參考網(wǎng)上一個(gè)例子摊滔,具體出處忘記了阴绢。若原作者看見(jiàn)店乐,請(qǐng)見(jiàn)諒。

public class MDProgressBar extends View {

    private final static String TAG = MDProgressBar.class.getSimpleName();

    private static final float DEFAULT_MAX_ANGLE = -305f;

    private static final float DEFAULT_MIN_ANGLE = -19f;

    //默認(rèn)的動(dòng)畫(huà)時(shí)間
    private static final int DEFAULT_DURATION = 660;

    private final static int DEFAULT_ARC_COLOR = Color.BLUE;
    //圓弧顏色
    private int arcColor = DEFAULT_ARC_COLOR;

    private AnimatorSet animatorSet;

    private float mBorderWidth;

    private Paint mPaint;

    private RectF arcRectF;

    private float startAngle = -45f;

    private float sweepAngle = -19f;

    private float incrementAngele = 0;
    //是否需要開(kāi)始繪制對(duì)勾
    private boolean isNeedTick = false;

    private int mResize;

    private TickAnimation mTickAnimation;
    //判斷"對(duì)勾"動(dòng)畫(huà)是否過(guò)半,"對(duì)勾"由兩條線繪制而成旱函。
    private boolean isAnimationOverHalf = false;
    //圓形進(jìn)度條的半徑
    private float mRadius;

    private float startY1;

    private float startX1;

    private float stopX1;

    private float stopY1;

    private float stopX2;

    private float stopY2;

    private OnPasswordCorrectlyListener mListener;

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

    public MDProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.materialStatusProgressAttr);
        arcColor = typedArray.getColor(R.styleable.materialStatusProgressAttr_arcColor, Color.parseColor("#4a90e2"));
        mBorderWidth = typedArray.getDimension(R.styleable.materialStatusProgressAttr_progressBarBorderWidth,
                getResources().getDimension(R.dimen.material_status_progress_border));
        typedArray.recycle();
        mPaint = new Paint();
        mPaint.setColor(arcColor);
        mPaint.setStrokeWidth(mBorderWidth);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        arcRectF = new RectF();
        mTickAnimation = new TickAnimation();
        mTickAnimation.setDuration(800);
        //對(duì)勾動(dòng)畫(huà)監(jiān)聽(tīng)
        mTickAnimation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                //當(dāng)對(duì)勾動(dòng)畫(huà)完成后,延遲一秒回掉,不然動(dòng)畫(huà)效果不明顯
                if (mListener != null) {
                    postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mListener.onPasswordCorrectly();

                        }
                    }, 1000);
                }
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
    }

    private void arcPaint() {
        mPaint.reset();
        mPaint.setColor(arcColor);
        mPaint.setStrokeWidth(mBorderWidth);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
    }

    private void linePaint() {
        mPaint.reset();
        mPaint.setColor(arcColor);
        mPaint.setStrokeWidth(mBorderWidth);
        mPaint.setAntiAlias(true);
    }
    //對(duì)勾動(dòng)畫(huà)完成回調(diào)
    public void setOnPasswordCorrectlyListener(OnPasswordCorrectlyListener listener) {
        this.mListener = listener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        startY1 = getMeasuredHeight() / 2;
        mRadius = getMeasuredHeight() / 2 - 2 * mBorderWidth;
        startX1 = startY1 - getMeasuredHeight() / 5;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        arcPaint();
        canvas.drawArc(arcRectF, startAngle + incrementAngele, sweepAngle, false, mPaint);
        if (animatorSet == null || !animatorSet.isRunning() && !isNeedTick) {
            startAnimation();
        }
        if (isNeedTick) {
            //補(bǔ)全圓
            arcPaint();
            canvas.drawArc(arcRectF, startAngle + incrementAngele + sweepAngle, 360 - sweepAngle, false, mPaint);
            linePaint();
            //畫(huà)第一根線
            canvas.drawLine(startX1, startY1, stopX1, stopY1, mPaint);
            if (isAnimationOverHalf) {
                //-2 +2 是為了兩根線盡可能靠攏
                canvas.drawLine(stopX1 - 2, stopY1 + 2, stopX2, stopY2, mPaint);
            }
        }
    }
    //對(duì)勾動(dòng)畫(huà)
    private class TickAnimation extends Animation {

        @Override
        protected void applyTransformation(final float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            if (interpolatedTime <= 0.5f) {
                stopX1 = startX1 + mRadius / 3 * interpolatedTime * 2;
                stopY1 = startY1 + mRadius / 3 * interpolatedTime * 2;
                isAnimationOverHalf = false;
            } else {
                stopX2 = stopX1 + (mRadius - 20) * (interpolatedTime - 0.5f) * 2;
                stopY2 = stopY1 - (mRadius - 20) * (interpolatedTime - 0.5f) * 2;
                isAnimationOverHalf = true;
            }
            invalidate();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mResize = (w < h) ? w : h;
        setBound();
    }

    private void setBound() {
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        arcRectF.set(paddingLeft + mBorderWidth, paddingTop + mBorderWidth, mResize - paddingLeft - mBorderWidth, mResize - paddingTop - mBorderWidth);
    }

    public void startAnimation() {
        isNeedTick = false;
        if (animatorSet != null && animatorSet.isRunning()) {
            animatorSet.cancel();
        }
        if (animatorSet == null) {
            animatorSet = new AnimatorSet();
        }
        AnimatorSet set = loopAnimator();
        animatorSet.play(set);
        animatorSet.addListener(new AnimatorListener() {

            private boolean isCancel = false;

            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (!isCancel) {
                    startAnimation();
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                isCancel = true;
            }
        });
        animatorSet.start();
    }

    /**
     * 進(jìn)度條旋轉(zhuǎn)的動(dòng)畫(huà)
     */
    private AnimatorSet loopAnimator() {
        //從小圈到大圈
        ValueAnimator holdAnimator1 = ValueAnimator.ofFloat(incrementAngele + DEFAULT_MIN_ANGLE, incrementAngele + 115f);
        holdAnimator1.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                incrementAngele = (float) animation.getAnimatedValue();
            }
        });
        holdAnimator1.setDuration(DEFAULT_DURATION);
        holdAnimator1.setInterpolator(new LinearInterpolator());
        ValueAnimator expandAnimator = ValueAnimator.ofFloat(DEFAULT_MIN_ANGLE, DEFAULT_MAX_ANGLE);
        expandAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                sweepAngle = (float) animation.getAnimatedValue();
                incrementAngele -= sweepAngle;
                invalidate();
            }
        });
        expandAnimator.setDuration(DEFAULT_DURATION);
        expandAnimator.setInterpolator(new DecelerateInterpolator(2));
        //從大圈到小圈
        ValueAnimator holdAnimator = ValueAnimator.ofFloat(startAngle, startAngle + 115f);
        holdAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                startAngle = (float) animation.getAnimatedValue();
            }
        });

        holdAnimator.setDuration(DEFAULT_DURATION);
        holdAnimator.setInterpolator(new LinearInterpolator());
        ValueAnimator narrowAnimator = ValueAnimator.ofFloat(DEFAULT_MAX_ANGLE, DEFAULT_MIN_ANGLE);
        narrowAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                sweepAngle = (float) animation.getAnimatedValue();
                invalidate();
            }
        });

        narrowAnimator.setDuration(DEFAULT_DURATION);
        narrowAnimator.setInterpolator(new DecelerateInterpolator(2));

        AnimatorSet set = new AnimatorSet();
        set.play(holdAnimator1).with(expandAnimator);
        set.play(holdAnimator).with(narrowAnimator).after(holdAnimator1);
        return set;
    }
    //清除動(dòng)畫(huà)
    private void cancelAnimator() {
        if (animatorSet != null) {
            animatorSet.cancel();
            isNeedTick = true;
        }
    }

    public void setSuccessfullyStatus() {
        if (animatorSet != null) {
            animatorSet.cancel();
            isNeedTick = true;
            startAnimation(mTickAnimation);
        }
    }

//重新setVisibility方法响巢,當(dāng)View不可見(jiàn)停止動(dòng)畫(huà),及時(shí)釋放資源棒妨。
    @Override
    public void setVisibility(int visibility) {
        switch (visibility) {
            case View.VISIBLE:
                startAnimation();
                break;
            case View.INVISIBLE:
                cancelAnimator();
                break;
            case View.GONE:
                cancelAnimator();
                break;
            default:
                break;
        }
        super.setVisibility(visibility);
    }

    public void setBorderWidth(int width) {
        this.mBorderWidth = width;
    }

    public void setArcColor(int color) {
        this.arcColor = color;
    }

    public interface OnPasswordCorrectlyListener {
        void onPasswordCorrectly();
    }

}

仿支付寶密碼輸入對(duì)話框

所有自定義控件完成踪古,最后就是組裝的過(guò)程。本文采用DialogFragment進(jìn)行封裝券腔。同時(shí)伏穆,提供各個(gè)點(diǎn)擊事件的回調(diào)。

public interface Callback {
   //忘記密碼
    void onForgetPassword();
   //密碼輸入完成纷纫,比如密碼長(zhǎng)度為六位枕扫,當(dāng)密碼輸入六位時(shí)候,直接 發(fā)出密碼校驗(yàn)請(qǐng)求
    void onPasswordCompleted(CharSequence password);
    //密碼輸入正確
    void onPasswordCorrectly();
   //取消彈出框
    void onCancel();
}

看源碼

public class PasswordKeypad extends DialogFragment implements View.OnClickListener, PasswordKeyboard.OnPasswordInputListener,
        MDProgressBar.OnPasswordCorrectlyListener {

    private TextView errorMsgTv;

    private Callback mCallback;

    private RelativeLayout passwordContainer;

    private MDProgressBar progressBar;

    private PasswordView passwordView;

    private int passwordCount;

    private boolean passwordState = true;

    PasswordKeyboard numberKeyBoard;

    private StringBuffer mPasswordBuffer = new StringBuffer();

    @Override
    public void onAttach(Activity context) {
        super.onAttach(context);
        if (context instanceof Callback) {
            mCallback = (Callback) context;
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.password_keypad, container, false);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(DialogFragment.STYLE_NO_TITLE, 0);
    }

    @Override
    public void onStart() {
        super.onStart();
        DisplayMetrics dm = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
        Window window = getDialog().getWindow();
        //去掉邊框
        window.setBackgroundDrawable(new ColorDrawable(0xffffffff));
        window.setLayout(dm.widthPixels, window.getAttributes().height);
        window.setWindowAnimations(R.style.exist_menu_animstyle);
        window.setGravity(Gravity.BOTTOM);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        errorMsgTv = (TextView) view.findViewById(R.id.error_msg);
        TextView forgetPasswordTv = (TextView) view.findViewById(R.id.forget_password);
        TextView cancelTv = (TextView) view.findViewById(R.id.cancel_dialog);

        passwordContainer = (RelativeLayout) view.findViewById(R.id.password_content);
        progressBar = (MDProgressBar) view.findViewById(R.id.password_progressBar);
        progressBar.setOnPasswordCorrectlyListener(this);
        passwordView = (PasswordView) view.findViewById(R.id.password_inputBox);
        //設(shè)置密碼長(zhǎng)度
        if (passwordCount > 0) {
            passwordView.setPasswordCount(passwordCount);
        }

        numberKeyBoard = (PasswordKeyboard) view.findViewById(R.id.password_keyboard);
        numberKeyBoard.setOnPasswordInputListener(this);

        cancelTv.setOnClickListener(this);
        forgetPasswordTv.setOnClickListener(this);
    }

    /**
     * 設(shè)置密碼長(zhǎng)度
     */
    public void setPasswordCount(int passwordCount) {
        this.passwordCount = passwordCount;
    }

    @Override
    public void onClick(View v) {
        if (R.id.cancel_dialog == v.getId()) {
            if (mCallback != null) {
                mCallback.onCancel();
            }
            dismiss();
        } else if (R.id.forget_password == v.getId()) {
            if (mCallback != null) {
                mCallback.onForgetPassword();
            }
        }
    }

    public void setCallback(Callback callBack) {
        this.mCallback = callBack;
    }

    public void setPasswordState(boolean correct) {
        setPasswordState(correct, "");
    }

    public void setPasswordState(boolean correct, String msg) {
        passwordState = correct;
        if (correct) {
            progressBar.setSuccessfullyStatus();
        } else {
            numberKeyBoard.resetKeyboard();
            passwordView.clearPassword();
            progressBar.setVisibility(View.GONE);
            passwordContainer.setVisibility(View.VISIBLE);
            errorMsgTv.setText(msg);
        }
    }

    @Override
    public void onPasswordCorrectly() {
        if (mCallback != null) {
            mCallback.onPasswordCorrectly();
        }
    }
    //開(kāi)始進(jìn)度條旋轉(zhuǎn)
    private void startLoading(CharSequence password) {
        passwordContainer.setVisibility(View.INVISIBLE);
        progressBar.setVisibility(View.VISIBLE);
        if (mCallback != null) {
            mCallback.onPasswordCompleted(password);
        }
    }

    @Override
    public void onInput(String character) {
        if (PasswordKeyboard.DEL.equals(character)) {
            if (mPasswordBuffer.length() > 0) {
                mPasswordBuffer.delete(mPasswordBuffer.length() - 1, mPasswordBuffer.length());
            }
        } else if (PasswordKeyboard.DONE.equals(character)) {
            dismiss();
        } else {
            //密碼輸入錯(cuò)誤狀態(tài)辱魁,再次輸入清除錯(cuò)誤提示文字
            if (!passwordState) {
                if (!TextUtils.isEmpty(errorMsgTv.getText())) {
                    errorMsgTv.setText("");
                }
            }
            mPasswordBuffer.append(character);
        }
        passwordView.setPassword(mPasswordBuffer);
        if (mPasswordBuffer.length() == passwordView.getPasswordCount()) {
            startLoading(mPasswordBuffer);
        }
    }
    //密碼對(duì)話框消失烟瞧,清除密碼輸入
    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        if (mPasswordBuffer.length() > 0) {
            mPasswordBuffer.delete(0, mPasswordBuffer.length());
        }
    }
}

如果本文對(duì)你有幫助,請(qǐng)不吝嗇你的喜歡染簇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末参滴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锻弓,更是在濱河造成了極大的恐慌砾赔,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件青灼,死亡現(xiàn)場(chǎng)離奇詭異暴心,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)杂拨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)专普,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人弹沽,你說(shuō)我怎么就攤上這事檀夹。” “怎么了贷币?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)亏狰。 經(jīng)常有香客問(wèn)我役纹,道長(zhǎng),這世上最難降的妖魔是什么暇唾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任促脉,我火速辦了婚禮辰斋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瘸味。我一直安慰自己宫仗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布旁仿。 她就那樣靜靜地躺著藕夫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枯冈。 梳的紋絲不亂的頭發(fā)上毅贮,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音尘奏,去河邊找鬼滩褥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛炫加,可吹牛的內(nèi)容都是我干的瑰煎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼俗孝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼酒甸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起驹针,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤烘挫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后柬甥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體饮六,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年苛蒲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卤橄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡臂外,死狀恐怖窟扑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漏健,我是刑警寧澤嚎货,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站蔫浆,受9級(jí)特大地震影響殖属,放射性物質(zhì)發(fā)生泄漏讯蒲。R本人自食惡果不足惜固耘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧隙袁,春花似錦棒动、人聲如沸婚脱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)玄组。三九已至滔驾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巧勤,已是汗流浹背嵌灰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颅悉,地道東北人沽瞭。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像剩瓶,于是被迫代替她去往敵國(guó)和親驹溃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容