Android 仿微信/支付宝的密码输入框效果(一)

来源:互联网 发布:odis工程师到数据 编辑:程序博客网 时间:2024/06/10 07:09

前言: 最近项目中碰到了一个像支付宝跟微信一样的输入密码自动验证的一个需求,因为之前在外包待过,所以你懂的!!用过太多封装好的控件了,都是略过,能实现功能就可以了,也都不管其实现过程,现在静下心了,于是打算研究研究。
先上一张类似需求的图片:
这里写图片描述
说明一下:本图片来自网络
然后先附上以前在外包用过的一个git链接:
https://github.com/Jungerr/GridPasswordView
效果为:
这里写图片描述

写的还是很不错的!! 在此也谢谢这位大牛提供的demo(^__^) 嘻嘻……

不罗嗦了!在做此功能之前先了解两个比较重要的知识点:
1、替换掉TextView默认的密文符号。
2、监听键盘的删除键。

1、替换掉TextView默认的密文符号:

我们先看看TextView的源码,当我们把TextView的inputType设置成密文的时候,里面做的操作:

if (allCaps) {            setTransformationMethod(new AllCapsTransformationMethod(getContext()));        }        if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {            setTransformationMethod(PasswordTransformationMethod.getInstance());            typefaceIndex = MONOSPACE;        } else if (mEditor != null &&                (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))                == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {            typefaceIndex = MONOSPACE;        }

我们可以看到,当我们设置成密文格式的时候,会走这么一段代码:

setTransformationMethod(PasswordTransformationMethod.getInstance());

我们重点看一下PasswordTransformationMethod:

/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package android.text.method;import android.os.Handler;import android.os.SystemClock;import android.graphics.Rect;import android.view.View;import android.text.Editable;import android.text.GetChars;import android.text.NoCopySpan;import android.text.TextUtils;import android.text.TextWatcher;import android.text.Spanned;import android.text.Spannable;import android.text.style.UpdateLayout;import java.lang.ref.WeakReference;public class PasswordTransformationMethodimplements TransformationMethod, TextWatcher{    public CharSequence getTransformation(CharSequence source, View view) {        if (source instanceof Spannable) {            Spannable sp = (Spannable) source;            /*             * Remove any references to other views that may still be             * attached.  This will happen when you flip the screen             * while a password field is showing; there will still             * be references to the old EditText in the text.             */            ViewReference[] vr = sp.getSpans(0, sp.length(),                                             ViewReference.class);            for (int i = 0; i < vr.length; i++) {                sp.removeSpan(vr[i]);            }            removeVisibleSpans(sp);            sp.setSpan(new ViewReference(view), 0, 0,                       Spannable.SPAN_POINT_POINT);        }        return new PasswordCharSequence(source);    }    public static PasswordTransformationMethod getInstance() {        if (sInstance != null)            return sInstance;        sInstance = new PasswordTransformationMethod();        return sInstance;    }    public void beforeTextChanged(CharSequence s, int start,                                  int count, int after) {        // This callback isn't used.    }    public void onTextChanged(CharSequence s, int start,                              int before, int count) {        if (s instanceof Spannable) {            Spannable sp = (Spannable) s;            ViewReference[] vr = sp.getSpans(0, s.length(),                                             ViewReference.class);            if (vr.length == 0) {                return;            }            /*             * There should generally only be one ViewReference in the text,             * but make sure to look through all of them if necessary in case             * something strange is going on.  (We might still end up with             * multiple ViewReferences if someone moves text from one password             * field to another.)             */            View v = null;            for (int i = 0; v == null && i < vr.length; i++) {                v = vr[i].get();            }            if (v == null) {                return;            }            int pref = TextKeyListener.getInstance().getPrefs(v.getContext());            if ((pref & TextKeyListener.SHOW_PASSWORD) != 0) {                if (count > 0) {                    removeVisibleSpans(sp);                    if (count == 1) {                        sp.setSpan(new Visible(sp, this), start, start + count,                                   Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);                    }                }            }        }    }    public void afterTextChanged(Editable s) {        // This callback isn't used.    }    public void onFocusChanged(View view, CharSequence sourceText,                               boolean focused, int direction,                               Rect previouslyFocusedRect) {        if (!focused) {            if (sourceText instanceof Spannable) {                Spannable sp = (Spannable) sourceText;                removeVisibleSpans(sp);            }        }    }    private static void removeVisibleSpans(Spannable sp) {        Visible[] old = sp.getSpans(0, sp.length(), Visible.class);        for (int i = 0; i < old.length; i++) {            sp.removeSpan(old[i]);        }    }    private static class PasswordCharSequence    implements CharSequence, GetChars    {        public PasswordCharSequence(CharSequence source) {            mSource = source;        }        public int length() {            return mSource.length();        }        public char charAt(int i) {            if (mSource instanceof Spanned) {                Spanned sp = (Spanned) mSource;                int st = sp.getSpanStart(TextKeyListener.ACTIVE);                int en = sp.getSpanEnd(TextKeyListener.ACTIVE);                if (i >= st && i < en) {                    return mSource.charAt(i);                }                Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);                for (int a = 0; a < visible.length; a++) {                    if (sp.getSpanStart(visible[a].mTransformer) >= 0) {                        st = sp.getSpanStart(visible[a]);                        en = sp.getSpanEnd(visible[a]);                        if (i >= st && i < en) {                            return mSource.charAt(i);                        }                    }                }            }            return DOT;        }        public CharSequence subSequence(int start, int end) {            char[] buf = new char[end - start];            getChars(start, end, buf, 0);            return new String(buf);        }        public String toString() {            return subSequence(0, length()).toString();        }        public void getChars(int start, int end, char[] dest, int off) {            TextUtils.getChars(mSource, start, end, dest, off);            int st = -1, en = -1;            int nvisible = 0;            int[] starts = null, ends = null;            if (mSource instanceof Spanned) {                Spanned sp = (Spanned) mSource;                st = sp.getSpanStart(TextKeyListener.ACTIVE);                en = sp.getSpanEnd(TextKeyListener.ACTIVE);                Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);                nvisible = visible.length;                starts = new int[nvisible];                ends = new int[nvisible];                for (int i = 0; i < nvisible; i++) {                    if (sp.getSpanStart(visible[i].mTransformer) >= 0) {                        starts[i] = sp.getSpanStart(visible[i]);                        ends[i] = sp.getSpanEnd(visible[i]);                    }                }            }            for (int i = start; i < end; i++) {                if (! (i >= st && i < en)) {                    boolean visible = false;                    for (int a = 0; a < nvisible; a++) {                        if (i >= starts[a] && i < ends[a]) {                            visible = true;                            break;                        }                    }                    if (!visible) {                        dest[i - start + off] = DOT;                    }                }            }        }        private CharSequence mSource;    }    private static class Visible    extends Handler    implements UpdateLayout, Runnable    {        public Visible(Spannable sp, PasswordTransformationMethod ptm) {            mText = sp;            mTransformer = ptm;            postAtTime(this, SystemClock.uptimeMillis() + 1500);        }        public void run() {            mText.removeSpan(this);        }        private Spannable mText;        private PasswordTransformationMethod mTransformer;    }    /**     * Used to stash a reference back to the View in the Editable so we     * can use it to check the settings.     */    private static class ViewReference extends WeakReference<View>            implements NoCopySpan {        public ViewReference(View v) {            super(v);        }    }    private static PasswordTransformationMethod sInstance;    private static char DOT = '\u2022';}

代码有点多,但是我们看到重点的一段代码就是:

 public char charAt(int i) {            .......            return DOT;        }

在charAt方法中返回了一个DOT字符,那么这个字符是什么呢?

private static char DOT = '\u2022';

我们可以看到,也就是我们用的“●”,也就是说android默认如果TextView设置成密文格式的话,都会用DOT 字符代替。知道了这个原理之后,我们就可以明确我们的目的了,继承PasswordTransformationMethod然后把DOT改成我们自己需要返回的字符即可,说干就干额。

首先,我们创建一个叫CustomPasswordTransformationMethod的去继承PasswordTransformationMethod:

package com.jungly.gridpasswordview;import android.text.method.PasswordTransformationMethod;import android.view.View;/** * @author EX_YINQINGYANG * @version [Android PABank C01, @2016-11-08] * @date 2016-11-08 * @description */public class MyCustomPwdMethod extends PasswordTransformationMethod{    /**     * 需要替换的点     */    private String dot;    public MyCustomPwdMethod(String dot){        this.dot=dot;    }    @Override    public CharSequence getTransformation(CharSequence source, View view) {        return new PasswordCharSequence(source);    }    private class PasswordCharSequence implements CharSequence {        private CharSequence mSource;        public PasswordCharSequence(CharSequence source) {            mSource = source;        }        @Override        public int length() {            return mSource.length();        }        @Override        public char charAt(int index) {            return dot.charAt(0);        }        @Override        public CharSequence subSequence(int start, int end) {            return mSource.subSequence(start, end);        }    }}

搞定,然后用的时候,我们只需要拿到TextView对象,然后调用:

tv.setTransformationMethod(new MyCustomPwdMethod("传入一个你需要替换的字符"));

2、监听键盘的删除键:
自定义叫MyEditText去继承EditText,然后重写TextView的onCreateInputConnection方法,监听系统键盘的操作。

 @Override    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {        return new ZanyInputConnection(super.onCreateInputConnection(outAttrs), true);    }    private class ZanyInputConnection extends InputConnectionWrapper {        public ZanyInputConnection(InputConnection target, boolean mutable) {            super(target, mutable);        }        @Override        public boolean sendKeyEvent(KeyEvent event) {            //当键盘点击删除键的时候            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {                if (delKeyEventListener != null) {                    delKeyEventListener.onDeleteClick();                    return true;                }            }            return super.sendKeyEvent(event);        }        @Override        public boolean deleteSurroundingText(int beforeLength, int afterLength) {            //因为我们在sendKeyEvent拦截掉了键盘的删除操作,所以我们在此发出一个keyevent代表按下删除键            if (beforeLength == 1 && afterLength == 0) {                return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));            }            return super.deleteSurroundingText(beforeLength, afterLength);        }    }

不得不说,这些大牛都是把源码跑了一遍啊,我压根都知道有这么一个api,哈哈!!!!!

搞定了上面两个问题了,我们接下来实现功能就容易了,未完待续哈…….

0 0
原创粉丝点击