Android 手势密码锁

简介: 现在有很多银行类APP、涉及到支付类的APP都集成了指纹、手势等二次验证功能,从而使APP获得更高的安全性。今天我们就来分析一下手势密码锁功能的具体实现。

一:简介

现在有很多银行类APP、涉及到支付类的APP都集成了指纹、手势等二次验证功能,从而使APP获得更高的安全性。今天我们就来分析一下手势密码锁功能的具体实现。

二:源码

源码Demo获取方法

关注 【网罗开发】微信公众号,回复【90】便可领取。
网罗天下方法,方便你我开发,所有文档会持续更新,欢迎关注一起成长!

三:demo源码分析

网上也不乏有一些手势密码的介绍,但是大多是使用第三方库,今天和大家介绍的是通过继承ViewGroup和View实现的原生手势密码锁。

项目文件

1.逻辑代码类分析

页面逻辑主要通过手势密码容器类(GestureContentView)、手势密码路径绘制类(GestureDrawline)、手势密码图案提示类(LockIndicator)三个类来实现。
核心代码如下:

package demo.gesturepsd.gesturepsd_android.widget;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import demo.gesturepsd.gesturepsd_android.R;
import demo.gesturepsd.gesturepsd_android.common.AppUtil;
import demo.gesturepsd.gesturepsd_android.entity.GesturePoint;
import demo.gesturepsd.gesturepsd_android.widget.GestureDrawline.GestureCallBack;

import java.util.ArrayList;
import java.util.List;

/**
 * 手势密码容器类
 *
 */
public class GestureContentView extends ViewGroup {

    private int baseNum = 6;

    private int[] screenDispaly;

    /**
     * 每个点区域的宽度
     */
    private int blockWidth;
    /**
     * 声明一个集合用来封装坐标集合
     */
    private List<GesturePoint> list;
    private Context context;
    private boolean isVerify;
    private GestureDrawline gestureDrawline;

    /**
     * 包含9个ImageView的容器,初始化
     * @param context
     * @param isVerify 是否为校验手势密码
     * @param passWord 用户传入密码
     * @param callBack 手势绘制完毕的回调
     */
    public GestureContentView(Context context, boolean isVerify, String passWord, GestureCallBack callBack) {
        super(context);
        screenDispaly = AppUtil.getScreenDispaly(context);
        blockWidth = screenDispaly[0]/3;
        this.list = new ArrayList<GesturePoint>();
        this.context = context;
        this.isVerify = isVerify;
        // 添加9个图标
        addChild();
        // 初始化一个可以画线的view
        gestureDrawline = new GestureDrawline(context, list, isVerify, passWord, callBack);
    }

    private void addChild(){
        for (int i = 0; i < 9; i++) {
            ImageView image = new ImageView(context);
            image.setBackgroundResource(R.drawable.gesture_node_normal);
            this.addView(image);
            invalidate();
            // 第几行
            int row = i / 3;
            // 第几列
            int col = i % 3;
            // 定义点的每个属性
            int leftX = col*blockWidth+blockWidth/baseNum;
            int topY = row*blockWidth+blockWidth/baseNum;
            int rightX = col*blockWidth+blockWidth-blockWidth/baseNum;
            int bottomY = row*blockWidth+blockWidth-blockWidth/baseNum;
            GesturePoint p = new GesturePoint(leftX, rightX, topY, bottomY, image,i+1);
            this.list.add(p);
        }
    }

    public void setParentView(ViewGroup parent){
        // 得到屏幕的宽度
        int width = screenDispaly[0];
        LayoutParams layoutParams = new LayoutParams(width, width);
        this.setLayoutParams(layoutParams);
        gestureDrawline.setLayoutParams(layoutParams);
        parent.addView(gestureDrawline);
        parent.addView(this);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            //第几行
            int row = i/3;
            //第几列
            int col = i%3;
            View v = getChildAt(i);
            v.layout(col*blockWidth+blockWidth/baseNum, row*blockWidth+blockWidth/baseNum,
                    col*blockWidth+blockWidth-blockWidth/baseNum, row*blockWidth+blockWidth-blockWidth/baseNum);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 遍历设置每个子view的大小
        for (int i = 0; i < getChildCount(); i++) {
            View v = getChildAt(i);
            v.measure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    /**
     * 保留路径delayTime时间长
     * @param delayTime
     */
    public void clearDrawlineState(long delayTime) {
        gestureDrawline.clearDrawlineState(delayTime);
    }

}

2.页面交互实现分析

设置手势密码页面(GestureEditActivity)

通过设置手势路径转换成数字密码,并通过SharedPreferences存储起来,代码如下:

package demo.gesturepsd.gesturepsd_android;

import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Html;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;

import demo.gesturepsd.gesturepsd_android.widget.GestureContentView;
import demo.gesturepsd.gesturepsd_android.widget.GestureDrawline.GestureCallBack;
import demo.gesturepsd.gesturepsd_android.widget.LockIndicator;

/**
 *
 * 手势密码设置界面
 *
 */
public class GestureEditActivity extends Activity implements OnClickListener {
    /** 手机号码*/
    public static final String PARAM_PHONE_NUMBER = "PARAM_PHONE_NUMBER";
    /** 意图 */
    public static final String PARAM_INTENT_CODE = "PARAM_INTENT_CODE";
    /** 首次提示绘制手势密码,可以选择跳过 */
    public static final String PARAM_IS_FIRST_ADVICE = "PARAM_IS_FIRST_ADVICE";

    private static final String fileName = "logintext";//定义保存的文件的名称

    private TextView mTextTitle;
    private TextView mTextCancel;
    private LockIndicator mLockIndicator;
    private TextView mTextTip;
    private FrameLayout mGestureContainer;
    private GestureContentView mGestureContentView;
    private TextView mTextReset;
    private String mParamSetUpcode = null;
    private String mParamPhoneNumber;
    private boolean mIsFirstInput = true;
    private String mFirstPassword = null;
    private String mConfirmPassword = null;
    private int mParamIntentCode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gesture_edit);
        setUpViews();
        setUpListeners();
    }

    private void setUpViews() {
        mTextTitle = (TextView) findViewById(R.id.text_title);
        mTextCancel = (TextView) findViewById(R.id.text_cancel);
        mTextReset = (TextView) findViewById(R.id.text_reset);
        mTextReset.setClickable(false);
        mLockIndicator = (LockIndicator) findViewById(R.id.lock_indicator);
        mTextTip = (TextView) findViewById(R.id.text_tip);
        mGestureContainer = (FrameLayout) findViewById(R.id.gesture_container);
        // 初始化一个显示各个点的viewGroup
        mGestureContentView = new GestureContentView(this, false, "", new GestureCallBack() {
            @Override
            public void onGestureCodeInput(String inputCode) {
                if (!isInputPassValidate(inputCode)) {
                    mTextTip.setText(Html.fromHtml("<font color='#c70c1e'>最少链接4个点, 请重新输入</font>"));
                    mGestureContentView.clearDrawlineState(0L);
                    return;
                }
                if (mIsFirstInput) {
                    mFirstPassword = inputCode;
                    updateCodeList(inputCode);
                    mGestureContentView.clearDrawlineState(0L);
                    mTextReset.setClickable(true);
                    mTextReset.setText(getString(R.string.reset_gesture_code));
                } else {
                    if (inputCode.equals(mFirstPassword)) {
                        Toast.makeText(GestureEditActivity.this, "设置成功", Toast.LENGTH_SHORT).show();
                        Log.i("charge password", mFirstPassword);

                        SharedPreferences share = getSharedPreferences(fileName, MODE_PRIVATE);
                        SharedPreferences.Editor editor = share.edit();
                        editor.putString("psdtype", mFirstPassword);
                        editor.commit();

                        mGestureContentView.clearDrawlineState(0L);
                        GestureEditActivity.this.finish();
                    } else {
                        mTextTip.setText(Html.fromHtml("<font color='#c70c1e'>与上一次绘制不一致,请重新绘制</font>"));
                        // 左右移动动画
                        Animation shakeAnimation = AnimationUtils.loadAnimation(GestureEditActivity.this, R.anim.shake);
                        mTextTip.startAnimation(shakeAnimation);
                        // 保持绘制的线,1.5秒后清除
                        mGestureContentView.clearDrawlineState(1300L);
                    }
                }
                mIsFirstInput = false;
            }

            @Override
            public void checkedSuccess() {

            }

            @Override
            public void checkedFail() {

            }
        });
        // 设置手势解锁显示到哪个布局里面
        mGestureContentView.setParentView(mGestureContainer);
        updateCodeList("");
    }

    private void setUpListeners() {
        mTextCancel.setOnClickListener(this);
        mTextReset.setOnClickListener(this);
    }

    private void updateCodeList(String inputCode) {
        // 更新选择的图案
        mLockIndicator.setPath(inputCode);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.text_cancel:
                this.finish();
                break;
            case R.id.text_reset:
                mIsFirstInput = true;
                updateCodeList("");
                mTextTip.setText(getString(R.string.set_gesture_pattern));
                break;
            default:
                break;
        }
    }

    private boolean isInputPassValidate(String inputPassword) {
        if (TextUtils.isEmpty(inputPassword) || inputPassword.length() < 4) {
            return false;
        }
        return true;
    }

}

校验手势密码页面(GestureVerifyActivity)

通过SharedPreferences获取到设置页面存储的密码与校验输入的密码进行对比,校验密码是否正确,源码如下:

package demo.gesturepsd.gesturepsd_android;

import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Html;
import android.text.TextUtils;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import demo.gesturepsd.gesturepsd_android.widget.GestureContentView;
import demo.gesturepsd.gesturepsd_android.widget.GestureDrawline.GestureCallBack;

/**
 *
 * 手势绘制/校验界面
 *
 */
public class GestureVerifyActivity extends Activity implements android.view.View.OnClickListener {
    /** 手机号码*/
    public static final String PARAM_PHONE_NUMBER = "PARAM_PHONE_NUMBER";
    /** 意图 */
    public static final String PARAM_INTENT_CODE = "PARAM_INTENT_CODE";

    private static final String fileName = "logintext";//定义保存的文件的名称

    private RelativeLayout mTopLayout;
    private TextView mTextTitle;
    private TextView mTextCancel;
    private ImageView mImgUserLogo;
    private TextView mTextPhoneNumber;
    private TextView mTextTip;
    private FrameLayout mGestureContainer;
    private GestureContentView mGestureContentView;
    private TextView mTextForget;
    private TextView mTextOther;
    private String mParamPhoneNumber;
    private long mExitTime = 0;
    private int mParamIntentCode;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gesture_verify);
        ObtainExtraData();
        setUpViews();
        setUpListeners();
    }

    private void ObtainExtraData() {
        mParamPhoneNumber = getIntent().getStringExtra(PARAM_PHONE_NUMBER);
        mParamIntentCode = getIntent().getIntExtra(PARAM_INTENT_CODE, 0);
    }

    private void setUpViews() {
        mTopLayout = (RelativeLayout) findViewById(R.id.top_layout);
        mTextTitle = (TextView) findViewById(R.id.text_title);
        mTextCancel = (TextView) findViewById(R.id.text_cancel);
        mImgUserLogo = (ImageView) findViewById(R.id.user_logo);
        mTextPhoneNumber = (TextView) findViewById(R.id.text_phone_number);
        mTextTip = (TextView) findViewById(R.id.text_tip);
        mGestureContainer = (FrameLayout) findViewById(R.id.gesture_container);
        mTextForget = (TextView) findViewById(R.id.text_forget_gesture);
        mTextOther = (TextView) findViewById(R.id.text_other_account);


        SharedPreferences share = super.getSharedPreferences(fileName,
                MODE_PRIVATE);

        String psdtype = share.getString("psdtype", null);

        // 初始化一个显示各个点的viewGroup
        mGestureContentView = new GestureContentView(this, true, psdtype,
                new GestureCallBack() {

                    @Override
                    public void onGestureCodeInput(String inputCode) {

                    }

                    @Override
                    public void checkedSuccess() {
                        mGestureContentView.clearDrawlineState(0L);
                        Toast.makeText(GestureVerifyActivity.this, "密码正确", 1000).show();
                        GestureVerifyActivity.this.finish();
                    }

                    @Override
                    public void checkedFail() {
                        mGestureContentView.clearDrawlineState(1300L);
                        mTextTip.setVisibility(View.VISIBLE);
                        mTextTip.setText(Html
                                .fromHtml("<font color='#c70c1e'>密码错误</font>"));
                        // 左右移动动画
                        Animation shakeAnimation = AnimationUtils.loadAnimation(GestureVerifyActivity.this, R.anim.shake);
                        mTextTip.startAnimation(shakeAnimation);
                    }
                });
        // 设置手势解锁显示到哪个布局里面
        mGestureContentView.setParentView(mGestureContainer);
    }

    private void setUpListeners() {
        mTextCancel.setOnClickListener(this);
        mTextForget.setOnClickListener(this);
        mTextOther.setOnClickListener(this);
    }

    private String getProtectedMobile(String phoneNumber) {
        if (TextUtils.isEmpty(phoneNumber) || phoneNumber.length() < 11) {
            return "";
        }
        StringBuilder builder = new StringBuilder();
        builder.append(phoneNumber.subSequence(0,3));
        builder.append("****");
        builder.append(phoneNumber.subSequence(7,11));
        return builder.toString();
    }



    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.text_cancel:
                this.finish();
                break;
            default:
                break;
        }
    }

}

主页面(MainActivity)

主页面是设置手势密码和校验手势密码的两个按钮,源码比较简单,不做展示。

四:Demo 截图

设置手势密码

校验手势密码

目录
相关文章
|
数据安全/隐私保护 Android开发
Android10.0 锁屏分析——KeyguardPINView PIN锁分析
Android10.0 锁屏分析——KeyguardPINView PIN锁分析
|
4月前
|
SQL 安全 Java
Android经典面试题之Kotlin中object关键字实现的是什么类型的单例模式?原理是什么?怎么实现双重检验锁单例模式?
Kotlin 单例模式概览 在 Kotlin 中,`object` 关键字轻松实现单例,提供线程安全的“饿汉式”单例。例如: 要延迟初始化,可使用 `companion object` 和 `lazy` 委托: 对于参数化的线程安全单例,结合 `@Volatile` 和 `synchronized`
63 6
|
5月前
|
安全 Android开发 Kotlin
Android面试题之Kotlin协程并发问题和互斥锁
Kotlin的协程提供轻量级并发解决方案,如`kotlinx.coroutines`库。`Mutex`用于同步,确保单个协程访问共享资源。示例展示了`withLock()`、`lock()`、`unlock()`和`tryLock()`的用法,这些方法帮助在协程中实现线程安全,防止数据竞争。
76 1
|
6月前
|
数据安全/隐私保护 Android开发
Android Studio APP实战开发之找回密码及忘记密码(附源码 超实用必看)
Android Studio APP实战开发之找回密码及忘记密码(附源码 超实用必看)
313 0
|
6月前
|
SQL 数据库 数据安全/隐私保护
Android Studio App开发中数据库SQLite的解析及实战使用(包括创建数据库,增删改查,记住密码等 附源码必看)
Android Studio App开发中数据库SQLite的解析及实战使用(包括创建数据库,增删改查,记住密码等 附源码必看)
469 0
|
数据安全/隐私保护 Android开发 iOS开发
解决第三方邮箱APP登陆QQ、163邮箱无法验证账户名或密码的问题(IOS、MacOS、Windows、Android)
解决第三方邮箱APP登陆QQ、163邮箱无法验证账户名或密码的问题(IOS、MacOS、Windows、Android)
220 0
|
Java 数据安全/隐私保护 Android开发
Android 实战项目 -- 登录主页、找回密码
Android 实战项目 -- 登录主页、找回密码
163 0
|
Java Android开发 数据安全/隐私保护
android10.0(Q) AOSP 增加应用锁功能
android10.0(Q) AOSP 增加应用锁功能
223 0
|
存储 数据库 数据安全/隐私保护
Android11.0(R) 预留清空锁屏密码接口
Android11.0(R) 预留清空锁屏密码接口
307 0
|
安全 算法 Java
Android10.0 锁屏分析——KeyguardPatternView图案锁分析
Android10.0 锁屏分析——KeyguardPatternView图案锁分析
Android10.0 锁屏分析——KeyguardPatternView图案锁分析
下一篇
无影云桌面