美文网首页Android项目登录
安卓指纹+密码支付(解锁)仿支付宝Demo

安卓指纹+密码支付(解锁)仿支付宝Demo

作者: chengww | 来源:发表于2017-05-14 17:16 被阅读519次

    1.前言

    Google从Android6.0(api23)就开始提供标准指纹识别支持,并对外提供指纹识别相关的接口。但是Android上的指纹识别似乎就是用来解锁手机屏幕,三方APP应用指纹的也是寥寥无几。一直想踩下安卓指纹识别的坑,直到这两天终于空出时间来尝试下android指纹识别的应用。

    好吧,废话说不下去了,直接上Demo截图:

    Screenrecorder-2017-05-14-02-50-58-795_20170514164200.gif

    2.使用指纹识别

    点击指纹识别button,弹出如图弹窗,弹窗使用DialogFragment。具体实现请看下面

     3.使用密码解锁
    
    Screenshot_2017-05-14-14-53-29-795_com.chengww.fingerdemo.png

    指纹识别的使用

    官方标准库
    Google提供的与指纹识别相关的核心类不多,主类是FingerprintManager,主类依赖三个内部类,如下图所示:


    FingerprintManager主要提供三个方法如下:

    FingerprintManager.AuthenticationCallback类提供的回调接口如下,重点区分红色下划线标注的部分

    启动指纹识别接口

    看了上面的介绍,如果要写代码就变得简单了

    1. AndroidManifest权限声明

    <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
    

    2. 获取FingerManager服务对象

    public static FingerprintManager getFingerprintManager(Context context) { 
        FingerprintManager fingerprintManager = null;
           try {
               fingerprintManager = (FingerprintManager)context.getSystemService(Context.FINGERPRINT_SERVICE); 
          } catch (Throwable e) { 
                Log.e("TAG","have not class FingerprintManager");
         } 
         return fingerprintManager;
    }
    

    3. 启动指纹识别

    mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, 0, mAuthCallback, null);
    

    官方v4兼容包

    上面介绍最标准的官方实现指纹识别的方式,当然适配肯定没这么简单,因为有很多设备兼容性要考虑,Google后续再v4包中提供了一套完整的实现,实现类与上面的一一对应的,就是改了个名字(FingerprintManager改为了FingerprintManagerCompat,机智的发现Compat是兼容的意思,所以Google在v4包中做了一些兼容性处理),做了很多兼容处理,官方推荐使用后者。v4包中类结构如下:



    v4包中的类使用与上面标准库中的一致,就是名字不一样而已,这里不再介绍使用方式。

    3.使用密码解锁

    指纹识别失败达到一定次数调用密码解锁,同指纹识别弹窗一样使用DialogFragment。用这个DialogFragment有个坑,稍后再讲。

    Screenshot_2017-05-14-14-53-47-312_com.chengww.fingerdemo.png

    密码解锁弹窗样式,fragment_pwd.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:layout_marginTop="100dp"
            android:background="@drawable/shape_dialog"
            android:orientation="vertical"
            android:paddingBottom="@dimen/spacing_large">
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <TextView
                    style="@style/style_black_normal_text"
                    android:layout_width="wrap_content"
                    android:layout_height="@dimen/text_item_height"
                    android:layout_centerInParent="true"
                    android:gravity="center"
                    android:text="请输入密码" />
    
                <ImageView
                    android:id="@+id/iv_close"
                    android:layout_width="20dp"
                    android:layout_height="20dp"
                    android:background="@drawable/selector_item_pressed"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_marginRight="@dimen/spacing_tiny"
                    android:src="@mipmap/icon_del" />
    
            </RelativeLayout>
    
            <View style="@style/style_separate_line" />
    
            <com.chengww.fingerdemo.PwdView
                android:id="@+id/pwdView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/spacing_large"
                android:layout_marginRight="@dimen/spacing_large"
                android:background="@color/white" />
    
            <TextView
                android:id="@+id/tv_miss_pwd"
                style="@style/style_blue_normal_text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/text_item_right_margin"
                android:layout_marginEnd="@dimen/text_item_right_margin"
                android:layout_marginRight="@dimen/text_item_right_margin"
                android:text="忘记密码?"
                android:background="@drawable/selector_item_pressed"
                android:layout_gravity="end"
                android:gravity="center" />
    
        </LinearLayout>
    
        <com.chengww.fingerdemo.InputMethodView
            android:id="@+id/inputMethodView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true" />
    
    </RelativeLayout>
    

    密码显示圆点框

    public class PwdView extends View {
    
        private ArrayList<String> result;//输入结果保存
        private int count;//密码位数
        private int size;//默认每一格的大小
        private Paint mBorderPaint;//边界画笔
        private Paint mDotPaint;//掩盖点的画笔
        private int mBorderColor;//边界颜色
        private int mDotColor;//掩盖点的颜色
        private RectF mRoundRect;//外面的圆角矩形
        private int mRoundRadius;//圆角矩形的圆角程度
    
        public PwdView(Context context) {
            super(context);
            init(null);
        }
    
        private InputCallBack inputCallBack;//输入完成的回调
        private InputMethodView inputMethodView; //输入键盘
    
    
        public interface InputCallBack {
            void onInputFinish(String result);
        }
    
        public PwdView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(attrs);
        }
    
        public PwdView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(attrs);
        }
    
        /**
         * 初始化相关参数
         */
        void init(AttributeSet attrs) {
            final float dp = getResources().getDisplayMetrics().density;
            this.setFocusable(true);
            this.setFocusableInTouchMode(true);
            result = new ArrayList<>();
            if (attrs != null) {
                TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.PwdView);
                mBorderColor = ta.getColor(R.styleable.PwdView_border_color, Color.LTGRAY);
                mDotColor = ta.getColor(R.styleable.PwdView_dot_color, Color.BLACK);
                count = ta.getInt(R.styleable.PwdView_count, 6);
                ta.recycle();
            } else {
                mBorderColor = Color.LTGRAY;
                mDotColor = Color.GRAY;
                count = 6;//默认6位密码
            }
            size = (int) (dp * 30);//默认30dp一格
            //color
            mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mBorderPaint.setStrokeWidth(3);
            mBorderPaint.setStyle(Paint.Style.STROKE);
            mBorderPaint.setColor(mBorderColor);
    
            mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mDotPaint.setStrokeWidth(3);
            mDotPaint.setStyle(Paint.Style.FILL);
            mDotPaint.setColor(mDotColor);
            mRoundRect = new RectF();
            mRoundRadius = (int) (5 * dp);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int w = measureWidth(widthMeasureSpec);
            int h = measureHeight(heightMeasureSpec);
            int wsize = MeasureSpec.getSize(widthMeasureSpec);
            int hsize = MeasureSpec.getSize(heightMeasureSpec);
            //宽度没指定,但高度指定
            if (w == -1) {
                if (h != -1) {
                    w = h * count;//宽度=高*数量
                    size = h;
                } else {//两个都不知道,默认宽高
                    w = size * count;
                    h = size;
                }
            } else {//宽度已知
                if (h == -1) {//高度不知道
                    h = w / count;
                    size = h;
                }
            }
            setMeasuredDimension(Math.min(w, wsize), Math.min(h, hsize));
        }
    
        private int measureWidth(int widthMeasureSpec) {
            //宽度
            int wmode = MeasureSpec.getMode(widthMeasureSpec);
            int wsize = MeasureSpec.getSize(widthMeasureSpec);
            if (wmode == MeasureSpec.AT_MOST) {//wrap_content
                return -1;
            }
            return wsize;
        }
    
        private int measureHeight(int heightMeasureSpec) {
            //高度
            int hmode = MeasureSpec.getMode(heightMeasureSpec);
            int hsize = MeasureSpec.getSize(heightMeasureSpec);
            if (hmode == MeasureSpec.AT_MOST) {//wrap_content
                return -1;
            }
            return hsize;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {//点击控件弹出输入键盘
                requestFocus();
                inputMethodView.setVisibility(VISIBLE);
                return true;
            }
            return true;
        }
    
        @Override
        protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
            super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
            if (gainFocus) {
                inputMethodView.setVisibility(VISIBLE);
            } else {
                inputMethodView.setVisibility(GONE);
            }
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            final int width = getWidth() - 2;
            final int height = getHeight() - 2;
            //先画个圆角矩形
            mRoundRect.set(0, 0, width, height);
            canvas.drawRoundRect(mRoundRect, 0, 0, mBorderPaint);
            //画分割线
            for (int i = 1; i < count; i++) {
                final int x = i * size;
                canvas.drawLine(x, 0, x, height, mBorderPaint);
            }
            //画掩盖点,
            // 这是前面定义的变量 private ArrayList<Integer> result;//输入结果保存
            int dotRadius = size / 8;//圆圈占格子的三分之一
            for (int i = 0; i < result.size(); i++) {
                final float x = (float) (size * (i + 0.5));
                final float y = size / 2;
                canvas.drawCircle(x, y, dotRadius, mDotPaint);
            }
        }
    
        @Override
        public boolean onCheckIsTextEditor() {
            return true;
        }
    
        @Override
        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
            outAttrs.inputType = InputType.TYPE_CLASS_NUMBER;//输入类型为数字
            outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
            return new MyInputConnection(this, false);
        }
    
        public void setInputCallBack(InputCallBack inputCallBack) {
            this.inputCallBack = inputCallBack;
        }
    
        public void clearResult() {
            result.clear();
            invalidate();
        }
    
    
        private class MyInputConnection extends BaseInputConnection {
            public MyInputConnection(View targetView, boolean fullEditor) {
                super(targetView, fullEditor);
            }
    
            @Override
            public boolean commitText(CharSequence text, int newCursorPosition) {
                //这里是接受输入法的文本的,我们只处理数字,所以什么操作都不做
                return super.commitText(text, newCursorPosition);
            }
    
            @Override
            public boolean deleteSurroundingText(int beforeLength, int afterLength) {
                //软键盘的删除键 DEL 无法直接监听,自己发送del事件
                if (beforeLength == 1 && afterLength == 0) {
                    return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
                            && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
                }
                return super.deleteSurroundingText(beforeLength, afterLength);
            }
        }
    
    
        /**
         * 设置输入键盘view
         *
         * @param inputMethodView
         */
        public void setInputMethodView(InputMethodView inputMethodView) {
            this.inputMethodView = inputMethodView;
            this.inputMethodView.setInputReceiver(new InputMethodView.InputReceiver() {
                @Override
                public void receive(String num) {
                    if (num.equals("-1")) {
                        if (!result.isEmpty()) {
                            result.remove(result.size() - 1);
                            invalidate();
                        }
                    } else {
                        if (result.size() < count) {
                            result.add(num);
                            invalidate();
                            ensureFinishInput();
                        }
                    }
    
    
                }
            });
        }
    
        /**
         * 判断是否输入完成,输入完成后调用callback
         */
        void ensureFinishInput() {
            if (result.size() == count && inputCallBack != null) {//输入完成
                StringBuffer sb = new StringBuffer();
                for (String i : result) {
                    sb.append(i);
                }
                inputCallBack.onInputFinish(sb.toString());
                clearResult();
            }
        }
    
        /**
         * 获取输入文字
         *
         * @return
         */
        public String getInputText() {
            if (result.size() == count) {
                StringBuffer sb = new StringBuffer();
                for (String i : result) {
                    sb.append(i);
                }
                return sb.toString();
            }
            return null;
        }
    }
    

    下方输入键盘

    public class InputMethodView extends LinearLayout implements View.OnClickListener {
    
        private InputReceiver inputReceiver;
    
        public InputMethodView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            LayoutInflater.from(context).inflate(R.layout.view_password_input, this);
    
            initView();
        }
    
        private void initView() {
            findViewById(R.id.btn_1).setOnClickListener(this);
            findViewById(R.id.btn_2).setOnClickListener(this);
            findViewById(R.id.btn_3).setOnClickListener(this);
            findViewById(R.id.btn_4).setOnClickListener(this);
            findViewById(R.id.btn_5).setOnClickListener(this);
            findViewById(R.id.btn_6).setOnClickListener(this);
            findViewById(R.id.btn_7).setOnClickListener(this);
            findViewById(R.id.btn_8).setOnClickListener(this);
            findViewById(R.id.btn_9).setOnClickListener(this);
            findViewById(R.id.btn_0).setOnClickListener(this);
            findViewById(R.id.btn_del).setOnClickListener(this);
    
            findViewById(R.id.layout_hide).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    setVisibility(GONE);
                }
            });
        }
    
        @Override
        public void onClick(View v) {
            String num = (String) v.getTag();
            this.inputReceiver.receive(num);
        }
    
    
        /**
         * 设置接收器
         * @param receiver
         */
        public void setInputReceiver(InputReceiver receiver){
            this.inputReceiver = receiver;
        }
    
        /**
         * 输入接收器
         */
        public interface InputReceiver{
    
            void receive(String num);
        }
    }
    

    MainActivity实现输入回调就可以得到回调结果了

    public class MainActivity extends AppCompatActivity implements PwdView.InputCallBack{
        @Override
        public void onInputFinish(String result) {
            if (result.equals("123456")) {
                fragment.dismiss();
                Toast.makeText(this, "验证成功", Toast.LENGTH_SHORT).show();
            }else {
                showPwdError();
            }
        }
    }
    

    今天暂时写这么多吧,整个项目还有点BUG,标题说仿支付宝也仿的不像,改天把后半部分整理出来修改下再发个完整版的。
    源代码下载:
    http://git.oschina.net/chengww5217/fingerdemo
    指纹解锁部分参考引用了以下文章,原作者指纹识别部分写的非常棒,强烈建议前往拜读:
    http://www.cnblogs.com/popfisher/p/6063835.html
    https://willowtreeapps.com/ideas/android-fingerprint-apis-an-overview-for-android-app-developers/

    相关文章

      网友评论

      • 七秒记忆_a451:验证指纹这里是怎么记录指纹的?
        chengww:@七秒记忆_a451 支付宝有手机厂商配合,直接调用手机厂商提供的接口,我们这是android系统提供的。不一样的
        七秒记忆_a451:@chengww 是不能录入,可以记录吧,支付宝是如何记录的。加q请教下:545988524
        chengww:@七秒记忆_a451 不能录入指纹,要去系统设置里面录入,和ios一样
      • 05373921d029:楼主棒棒哒!
      • 8321:支持下

      本文标题:安卓指纹+密码支付(解锁)仿支付宝Demo

      本文链接:https://www.haomeiwen.com/subject/skeqxxtx.html