Android开发 多语言、指纹登录、手势登录

简介

随着互联网的发展不光手机配置越来越高,app产品要求也越来越高了。现在做APP时产品都会让我们加上 多语言、指纹登录、手势登录等功能。下面代码完全适配Android8.0

其它文章

OkHttp3简单使用和封装使用
Retrofit2.0+RxJava2.0封装使用
Android使用IconFont阿里矢量图标
Android Studio 使用SVN 主干和分支合并代码

项目地址:https://github.com/pengjunshan/LanguageFingerprintGesture

效果图

多语言
多语言.gif
指纹登录
指纹登录.gif
手势登录
手势登录.gif
多语言

多语言可以实现国际化,但是并不是app上所有的地方都是用国际化语言,这个根据实际需求来做。这里做的是英语,适配8.0
1.改变app语言环境后需要重新加载资源文件
2.改变app语言环境后杀死进程重启仍是改变后的语言

1.第一步

首先在res资源文件下创建一个英语资源文件,res右键>New>Android Resource Directory(进入第二步)

第一步.png
2.第二步

选择Locale 然后点击箭头选择语言(进入第三步)

第二步.png
3.第三步

滑动Language滚动条可以查看所有支持的语言,然后选择en:English,点击OK(进入第四步)

第三步.png
4.第四步

进行完第三步后可以看到res文件夹下多了一个values-en文件夹,这个就是我们刚创建的英语资源文件,然后我们在此右键创建一个strings文件来存储英语资源。(名字一定要是strings)

第四步
5.第五步

把需要做国际化的资源文件在英语strings.xml中放入响应的资源。
大家看到中文中的资源比英文中的多,中文里会报错提示英文资源文件中少了这个资源,只是警告不影响编译。

资源文件.png
6.第六步

然后开始核心代码,布局中使用两个RadioButton来选择中文||英文。一个保存按钮来修改语言设置和保存到本地标识。
每次进入选择语言界面时先判断当前使用的是什么语言来设置哪个RadioButton为true。

修改语言后一定要重新加载资源,跳到主页面设置Flage为Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK杀死所有activity重新打开主页面 资源也就会重新加载

package com.kelin.languagefingerprintgesture;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.AppCompatButton;
import android.text.TextUtils;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.kelin.languagefingerprintgesture.utils.LocaleManager;
import com.kelin.languagefingerprintgesture.utils.PreferenceUtils;
/**
 * @author:PengJunShan. 时间:On 2019-04-22.
 *
 * 描述:切换多语言
 */
public class LanguageActivity extends AppCompatActivity {

  @BindView(R.id.rbChinese)
  RadioButton rbChinese;
  @BindView(R.id.rbEnglish)
  RadioButton rbEnglish;
  @BindView(R.id.rgLanguages)
  RadioGroup rgLanguages;
  @BindView(R.id.commit)
  AppCompatButton commit;

  private String mLanguageType;
  private boolean languageType;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_language);
    ButterKnife.bind(this);
    initView();
  }

  private void initView() {
    /**
     * 初始化判断使用的是什么语言
     */
    if (PreferenceUtils.getBoolean("isChinese", true)) {
      rbChinese.setChecked(true);
    } else {
      rbEnglish.setChecked(true);
    }
    /**
     * 监听RadioGroup
     */
    rgLanguages.setOnCheckedChangeListener(new OnCheckedChangeListener() {
      @Override
      public void onCheckedChanged(RadioGroup group, int checkedId) {
        switch (checkedId) {
          case R.id.rbChinese:
            mLanguageType = LocaleManager.LANGUAGE_CHINESE;
            languageType = true;
            break;

          case R.id.rbEnglish:
            mLanguageType = LocaleManager.LANGUAGE_ENGLISH;
            languageType = false;
            break;
        }
      }
    });
  }

  @OnClick(R.id.commit)
  public void onViewClicked() {
    if (!TextUtils.isEmpty(mLanguageType)) {
      /**
       * 修改语言
       */
      LocaleManager.setNewLocale(LanguageActivity.this, mLanguageType);
      /**
       * 保存使用语言标识
       */
      PreferenceUtils.commitBoolean("isChinese", languageType);
      /**
       * 跳转到主页 杀死其它所有的页面 重新加载资源文件
       */
      Intent i = new Intent(LanguageActivity.this, MainActivity.class);
      startActivity(i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK));
      finish();
    }
  }

}

在BaseActivity中重写 @Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleManager.setLocale(base));
}
所有Activity都要继承BaseActivity每次初始化时都会设置Locale

  @Override
  protected void attachBaseContext(Context base) {
    super.attachBaseContext(LocaleManager.setLocale(base));
  }

每次运行app时都要初始化语言设置Locale,一般写在自定义的Application中。

package com.kelin.languagefingerprintgesture.base;

import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import com.kelin.languagefingerprintgesture.utils.LocaleManager;
import com.kelin.languagefingerprintgesture.utils.PreferenceUtils;

/**
 * 作者:PengJunShan.
 *
 * 时间:On 2019-04-22.
 *
 * 描述:
 */
public class MyApplication extends Application {

  public static Context context;

  @Override
  public void onCreate() {
    super.onCreate();
    context = getApplicationContext();
    /**
     * 初始化SP
     */
    PreferenceUtils.init(context, "TRIP");

    /**
     * 初始化语言
     */
    LocaleManager.setLocale(this);
  }

  @Override
  protected void attachBaseContext(Context base) {
    super.attachBaseContext(LocaleManager.setLocale(base));
  }

  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    LocaleManager.setLocale(this);
  }

}

核心类LocaleManager类,主要是更新Locale,存取语言标识。代码中都有注释。
这里要说下8.0适配,刚开始写的时候app运行在8.0以下没有问题,8.0则不成功。在android o上,google改变了locale的规则。之前只存在一个locale,而后面是可以支持一个locale list。代码中时修改后的。

package com.kelin.languagefingerprintgesture.utils;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import java.util.Locale;

public class LocaleManager {

    /**
     * 中文
     */
    public static final String LANGUAGE_CHINESE = "zh";

    /**
     * 英文
     */
    public static final String LANGUAGE_ENGLISH = "en";

    /**
     * 初始化语言设置
     */
    public static Context setLocale(Context c) {
        return updateResources(c, getLanguage());
    }

    /**
     * 设置语言
     * @param c
     * @param language
     * @return
     */
    public static Context setNewLocale(Context c, String language) {
        persistLanguage(language);
        return updateResources(c, language);
    }

    /**
     * 得到语言设置
     * @return
     */
    public static String getLanguage() {
        return PreferenceUtils.getString(Constants.LANGUAGE_KEY, LANGUAGE_CHINESE);
    }

    /**
     * 存储设置的语言
     * @param language
     */
    @SuppressLint("ApplySharedPref")
    private static void persistLanguage(String language) {
        PreferenceUtils.commitString(Constants.LANGUAGE_KEY, language);
    }

    /**
     * 更新Locale
     * 适配8.0
     * @param language
     * @return
     */
    private static Context updateResources(Context context, String language) {
        Locale locale = new Locale(language);
        Locale.setDefault(locale);
        Resources res = context.getResources();
        Configuration configuration = res.getConfiguration();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            configuration.setLocale(locale);
            LocaleList localeList = new LocaleList(locale);
            LocaleList.setDefault(localeList);
            configuration.setLocales(localeList);
            context = context.createConfigurationContext(configuration);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            configuration.setLocale(locale);
            context = context.createConfigurationContext(configuration);
        }
        return context;
    }

}
指纹登录

指纹登录可以实现快捷登录,在Android6.0谷歌才提供统一指纹SDK接口,在6.0之前都是各个厂商自定义。
1.开启指纹登录时需验证你的指纹
2.关闭指纹登录时需弹出对话框确认

  • 之前实现指纹解锁都是用的FingerprintManager类,FingerprintManager在最新的Android 9.0系统上已经被废弃了,当Google在v4包中把FingerprintManager改为了FingerprintManagerCompat,而Compat是兼容的意思,所以Google在v4包中做了一些兼容性处理,官方推荐使用后者。所以本demo用的就是FingerprintManagerCompat工具类。

  • FingerprintManagerCompat: 指纹管理工具类

  • FingerprintManagerCompat.AuthenticationCallback :使用验证的时候传入该接口,通过该接口进行验证结果回调

  • FingerprintManagerCompat.CryptoObject: FingerprintManagerCompat支持的分装加密对象的类

申请权限

<!-- 指纹权限 -->
  <uses-permission android:name="android.permission.USE_FINGERPRINT"/>

首先判断当前手机设备是否支持指纹解锁,然后判断是否添加过指纹至少添加一个指纹。

FingerprintManagerCompat提供了三个方法:
  • isHardwareDetected() 判断是否有硬件支持
  • isKeyguardSecure() 判断是否设置锁屏,因为一个手机最少要有两种登录方式
  • hasEnrolledFingerprints() 判断系统中是否添加至少一个指纹
 /**
   *判断是否支持指纹识别
   */
  public static boolean supportFingerprint(Context mContext) {
    if (VERSION.SDK_INT < 23) {
//      MyToast.showToast("您的系统版本过低,不支持指纹功能");
      return false;
    } else {
      KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
      FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(mContext);
      if (!fingerprintManager.isHardwareDetected()) {
        MyToast.showToast("您的手机不支持指纹功能");
        return false;
      } else if (!keyguardManager.isKeyguardSecure()) {
        MyToast.showToast("您还未设置锁屏,请先设置锁屏并添加一个指纹");
        return false;
      } else if (!fingerprintManager.hasEnrolledFingerprints()) {
        MyToast.showToast("您至少需要在系统设置中添加一个指纹");
        return false;
      }
    }
    return true;
  }

生成一个对称加密的key (下载demo在ToolUtils中)

  @TargetApi(23)
  public static void initKey() {
    try {
      keyStore = KeyStore.getInstance("AndroidKeyStore");
      keyStore.load(null);
      KeyGenerator keyGenerator = KeyGenerator
          .getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
      Builder builder = new Builder(DEFAULT_KEY_NAME,
          KeyProperties.PURPOSE_ENCRYPT |
              KeyProperties.PURPOSE_DECRYPT)
          .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
          .setUserAuthenticationRequired(true)
          .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
      keyGenerator.init(builder.build());
      keyGenerator.generateKey();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

生成一个Cipher对象,(下载demo在ToolUtils中)

  @TargetApi(23)
  public static Cipher initCipher() {
    try {
      SecretKey key = (SecretKey) keyStore.getKey(DEFAULT_KEY_NAME, null);
      cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
          + KeyProperties.BLOCK_MODE_CBC + "/"
          + KeyProperties.ENCRYPTION_PADDING_PKCS7);
      cipher.init(Cipher.ENCRYPT_MODE, key);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    return cipher;
  }

创建FingerprintManagerCompat指纹管理工具类,谷歌已经不推荐使用FingerprintManager类。

FingerprintManagerCompat fingerprintManagerCompat =  FingerprintManagerCompat.from(mActivity);

拿到FingerprintManagerCompat对象后就可以调authenticate方法进行指纹识别了,先看下官网给的参数。

图片.png
  • CryptoObject这是一个加密类的对象,指纹扫描器会使用这个对象来判断认证结果的合法性。这个对象可以是null,但是这样的话,就意味这app无条件信任认证的结果,虽然从理论上这个过程可能被攻击,数据可以被篡改,这是app在这种情况下必须承担的风险。因此,建议这个参数不要置为null。
 FingerprintManagerCompat.CryptoObject cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
  • flags 标识位,根据上图的文档描述,这个位暂时应该为0,这个标志位应该是保留将来使用的。我们传0
  • cancel这个是CancellationSignal类的一个对象,这个对象用来在指纹识别器扫描用户指纹的是时候取消当前的扫描操作,如果不取消的话,那么指纹扫描器会移植扫描直到超时(一般为30s,取决于具体的厂商实现),这样的话就会比较耗电。建议这个参数不要置为null。识别过程中可以手动取消指纹识别
CancellationSignal mCancellationSignal = new CancellationSignal();
//识别过程中可以手动取消指纹识别
// mCancellationSignal.cancel();
  • callback这个是FingerprintManagerCompat.AuthenticationCallback类的对象,这个是这个接口中除了第一个参数之外最重要的参数了,稍后我们详细来介绍。这个参数不能为NULL。
 public class MyCallBack extends FingerprintManagerCompat.AuthenticationCallback {

        // 当出现错误的时候回调此函数,比如多次尝试都失败了的时候,errString是错误信息
        @Override
        public void onAuthenticationError(int errMsgId, CharSequence errString) {
            if (!isSelfCancelled) {
                errorMsg.setText(errString);
                Log.e("TAG", "errMsgId="+errMsgId);
                Toast.makeText(mActivity, "errMsgId="+errMsgId, Toast.LENGTH_SHORT).show();
                if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
                    Log.e("TAG", ""+errString);
                    dismiss();
                }
            }
        }

        // 当指纹验证失败的时候会回调此函数,失败之后允许多次尝试,失败次数过多会停止响应一段时间然后再停止sensor的工作
        @Override
        public void onAuthenticationFailed() {
            errorMsg.setText("指纹认证失败,请再试一次");
            Log.e("TAG", "onAuthenticationFailed");
        }

        //错误时提示帮助,比如说指纹错误,我们将显示在界面上 让用户知道情况
        @Override
        public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
            errorMsg.setText(helpString);
            Log.e("TAG", "helpString="+helpString);
        }

        // 当验证的指纹成功时会回调此函数,然后不再监听指纹sensor
        @Override
        public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
            if(onFingerprintSetting!=null) {
                onFingerprintSetting.onFingerprint(true);
            }
            dismiss();
        }
    }
  • handler 这是Handler类的对象,如果这个参数不为null的话,那么FingerprintManagerCompat将会使用这个handler中的looper来处理来自指纹识别硬件的消息。通常来讲,开发这不用提供这个参数,可以直接置为null,因为FingerprintManagerCompat会默认使用app的main looper来处理。我们传null

这里就要介绍的是上面提到的FingerprintManagerCompat.AuthenticationCallback了,因为扫描指纹和认证的过程都是在另外一个进程中完成的,所以我们需要采取异步的方式,等操作完成之后,让系统回调给我们,回调方法就是AuthenticationCallback类中的4个方法了

  • OnAuthenticationError(int errorCode, ICharSequence errString) 这个接口会再系统指纹认证出现不可恢复的错误的时候才会调用,当出现错误的时候回调此函数,比如多次尝试都失败了的时候,errString是错误信息并且参数errorCode就给出了错误码。
  • OnAuthenticationFailed()这个接口会在系统指纹认证失败的情况的下才会回调。注意这里的认证失败和上面的认证错误是不一样的,认证失败是指所有的信息都采集完整,并且没有任何异常,但是这个指纹和之前注册的指纹是不相符的;但是认证错误是指在采集或者认证的过程中出现了错误,比如指纹传感器工作异常等。也就是说认证失败是一个可以预期的正常情况,而认证错误是不可预期的异常情况。
  • OnAuthenticationHelp(int helpMsgId, ICharSequence helpString) 错误时提示帮助,比如说指纹错误,我们将显示在界面上 让用户知道情况,给出了helpString错误信息。
    +OnAuthenticationSucceeded(FingerprintManagerCompati.AuthenticationResult result)这个接口会在认证成功之后回调。我们可以在这个方法中提示用户认证成功,然后不再监听指纹sensor。
手势解锁

手势登录就是保存手势设置的密码,解锁时进行对比。
我的这个手势解锁是用的别人写好的我拿来稍稍修改一下,大神项目连接GestureLockView
我的demo中使用思路:
1.设置手势密码时,两次手势不同时手势路径变红+震动,可以重置。
2.修改密码时,需验证是否是本人。
3.解锁时,手势错误时手势路径变红+震动。

震动权限

<!-- 震动权限 -->
  <uses-permission android:name="android.permission.VIBRATE"/>

设置手势密码布局

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >
  <android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    >
    <!--设置手势解锁时提示view-->
 <com.kelin.languagefingerprintgesture.gesture.GestureLockDisplayView
      android:id="@+id/display_view"
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:layout_gravity="center_horizontal"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      />

    <TextView
      android:id="@+id/setting_hint"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center_horizontal"
      android:layout_marginTop="10dp"
      android:text="绘制解锁图案"
      app:layout_constraintTop_toBottomOf="@+id/display_view"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      />

    <TextView
      android:id="@+id/hintTV"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="与第一次设置密码不同,请再次设置"
      android:textColor="#FC6265"
      android:textSize="14sp"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/setting_hint"
      android:layout_marginTop="10dp"
      android:visibility="invisible"
      />

    <!--手势解锁view-->
    <com.kelin.languagefingerprintgesture.gesture.GestureLockLayout
      android:id="@+id/gesture_view"
      android:layout_width="300dp"
      android:layout_height="300dp"
      app:layout_constraintTop_toBottomOf="@+id/hintTV"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      />


    <TextView
      android:id="@+id/reSet"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="重新设置"
      app:layout_constraintTop_toBottomOf="@+id/gesture_view"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      android:textSize="16sp"
      android:textColor="#333333"
      android:layout_marginTop="10dp"
      />
  </android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>

设置手势密码,activity中初始化控件,监听手势密码

package com.kelin.languagefingerprintgesture;

import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import com.kelin.languagefingerprintgesture.gesture.GestureLockDisplayView;
import com.kelin.languagefingerprintgesture.gesture.GestureLockLayout;
import com.kelin.languagefingerprintgesture.gesture.GestureLockLayout.OnLockResetListener;
import com.kelin.languagefingerprintgesture.utils.Constants;
import com.kelin.languagefingerprintgesture.utils.PreferenceUtils;
import com.kelin.languagefingerprintgesture.utils.ToolUtils;
import java.util.ArrayList;
import java.util.List;

/**
 * @author:PengJunShan.

 * 时间:On 2019-04-22.

 * 描述:手势登录
 */
public class SetGestureLockActivity extends AppCompatActivity {

  @BindView(R.id.display_view)
  GestureLockDisplayView mLockDisplayView;
  @BindView(R.id.setting_hint)
  TextView mSettingHintText;
  @BindView(R.id.gesture_view)
  GestureLockLayout mGestureLockLayout;
  @BindView(R.id.reSet)
  TextView reSet;
  @BindView(R.id.hintTV)
  TextView hintTV;
  private Animation animation;
  private Context mContext;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_gesture_login);
    ButterKnife.bind(this);
    initView();
  }

  private void initView() {
    mContext = this;
    //设置提示view 每行每列点的个数
    mLockDisplayView.setDotCount(3);
    //设置提示view 选中状态的颜色
    mLockDisplayView.setDotSelectedColor(Color.parseColor("#01367A"));
    //设置提示view 非选中状态的颜色
    mLockDisplayView.setDotUnSelectedColor(Color.parseColor("#999999"));
    //设置手势解锁view 每行每列点的个数
    mGestureLockLayout.setDotCount(3);
    //设置手势解锁view 最少连接数
    mGestureLockLayout.setMinCount(4);
    //设置手势解锁view 模式为重置密码模式
    mGestureLockLayout.setMode(GestureLockLayout.RESET_MODE);

    //初始化动画
    animation = AnimationUtils.loadAnimation(this, R.anim.shake);
    initEvents();
  }

  private void initEvents() {

    mGestureLockLayout.setOnLockResetListener(new OnLockResetListener() {
      @Override
      public void onConnectCountUnmatched(int connectCount, int minCount) {
        //连接数小于最小连接数时调用
        mSettingHintText.setText("最少连接" + minCount + "个点");
        resetGesture();
      }

      @Override
      public void onFirstPasswordFinished(List<Integer> answerList) {
        //第一次绘制手势成功时调用
        Log.e("TAG","第一次密码=" + answerList);
        mSettingHintText.setText("确认解锁图案");
        //将答案设置给提示view
        mLockDisplayView.setAnswer(answerList);
        //重置
        resetGesture();
      }

      @Override
      public void onSetPasswordFinished(boolean isMatched, List<Integer> answerList) {
        //第二次密码绘制成功时调用
        Log.e("TAG","第二次密码=" + answerList.toString());
        if (isMatched) {
          //两次答案一致,保存
          PreferenceUtils.commitString(Constants.GESTURELOCK_KEY, answerList.toString());
          setResult(RESULT_OK);
          finish();
        } else {
          hintTV.setVisibility(View.VISIBLE);
          ToolUtils.setVibrate(mContext);
          hintTV.startAnimation(animation);
          mGestureLockLayout.startAnimation(animation);
          resetGesture();
        }
      }
    });
  }

  /**
   * 重置手势布局(只是布局)
   */
  private void resetGesture() {
    new Handler().postDelayed(new Runnable() {
      @Override
      public void run() {
        mGestureLockLayout.resetGesture();
      }
    }, 300);
  }

  /**
   * 重置手势布局(布局加逻辑)
   */
  @OnClick(R.id.reSet)
  public void onViewClicked() {
    mGestureLockLayout.setOnLockResetListener(null);
    mSettingHintText.setText("绘制解锁图案");
    mLockDisplayView.setAnswer(new ArrayList<Integer>());
    mGestureLockLayout.resetGesture();
    mGestureLockLayout.setMode(GestureLockLayout.RESET_MODE);
    hintTV.setVisibility(View.INVISIBLE);
    initEvents();
  }

}

录制GIF

手势解锁+指纹解锁布局,一般解锁时手势和指纹在一个activity中显示。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >

  <android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    >
    <TextView
      android:id="@+id/name"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="152****2877 下午好"
      android:textColor="#333"
      android:textStyle="bold"
      android:textSize="18sp"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      android:visibility="invisible"
      />

    <TextView
      android:id="@+id/hintTV"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="您还有5次机会"
      android:textColor="#FC6265"
      android:textSize="14sp"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/name"
      android:layout_marginTop="30dp"
      android:visibility="invisible"
      />

    <com.kelin.languagefingerprintgesture.gesture.GestureLockLayout
      android:id="@+id/gestureLock"
      android:layout_width="300dp"
      android:layout_height="300dp"
      android:layout_gravity="center_horizontal"
      android:layout_marginTop="20dp"
      app:layout_constraintTop_toBottomOf="@+id/hintTV"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      />

    <android.support.constraint.Group
      android:id="@+id/group"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      app:constraint_referenced_ids="name,gestureLock"
      android:visibility="visible"
      />
  </android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>

手势解锁 activity中初始化控件 监听手势信息

package com.kelin.languagefingerprintgesture;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.constraint.Group;
import android.text.TextUtils;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.kelin.languagefingerprintgesture.FingerprintDialogFragment.OnFingerprintSetting;
import com.kelin.languagefingerprintgesture.base.BaseActivity;
import com.kelin.languagefingerprintgesture.gesture.GestureLockLayout;
import com.kelin.languagefingerprintgesture.gesture.GestureLockLayout.OnLockVerifyListener;
import com.kelin.languagefingerprintgesture.utils.Constants;
import com.kelin.languagefingerprintgesture.utils.MyToast;
import com.kelin.languagefingerprintgesture.utils.PreferenceUtils;
import com.kelin.languagefingerprintgesture.utils.ToolUtils;
import javax.crypto.Cipher;
/**
 * @author:PengJunShan.

 * 时间:On 2019-04-26.

 * 描述:解锁
 */
public class GestureLockActivity extends BaseActivity {
  @BindView(R.id.gestureLock)
  GestureLockLayout mGestureLockLayout;
  @BindView(R.id.hintTV)
  TextView hintTV;
  @BindView(R.id.name)
  TextView name;
  @BindView(R.id.group)
  Group group;
  private Context mContext;
  private Animation animation;

  /**
   * 最大解锁次数
   */
  private int mNumber = 5;
  /**
   * change:修改手势  login:登录
   */
  private String type;

  /**
   * true:设置   false:未设置
   */
  private Boolean isFingerprint, isGesture;

  private FingerprintDialogFragment dialogFragment;
  private Cipher cipher;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_gesture_lock);
    ButterKnife.bind(this);
    initView();
  }

  protected void initView() {
    mContext = this;
    type = getIntent().getStringExtra("type");
    isGesture = PreferenceUtils.getBoolean(Constants.ISGESTURELOCK_KEY, false);
    isFingerprint = PreferenceUtils.getBoolean(Constants.ISFINGERPRINT_KEY, false);
    if (isGesture) {
      group.setVisibility(View.VISIBLE);
      setGestureListener();
    }

    if ("login".equals(type) && isFingerprint) {
      setFingerprint();
    }

  }

  private void setFingerprint() {
    if (ToolUtils.supportFingerprint(this)) {
      ToolUtils.initKey(); //生成一个对称加密的key
      //生成一个Cipher对象
      cipher = ToolUtils.initCipher();
    }
    if (cipher != null) {
      showFingerPrintDialog(cipher);
    }
  }

  private void showFingerPrintDialog(Cipher cipher) {
    dialogFragment = new FingerprintDialogFragment();
    dialogFragment.setCipher(cipher);
    dialogFragment.show(getSupportFragmentManager(), "fingerprint");

    dialogFragment.setOnFingerprintSetting(new OnFingerprintSetting() {
      @Override
      public void onFingerprint(boolean isSucceed) {
        if (isSucceed) {
          MyToast.showToastLong("指纹解锁成功!");
          startActivity(MainActivity.class);
          finish();
        } else {
          MyToast.showToastLong("指纹解锁失败!");
        }
      }
    });
  }

  private void setGestureListener() {
    String gestureLockPwd = PreferenceUtils.getString(Constants.GESTURELOCK_KEY, "");
    if (!TextUtils.isEmpty(gestureLockPwd)) {
      mGestureLockLayout.setAnswer(gestureLockPwd);
    } else {
      MyToast.showToast("没有设置过手势密码");
    }
    mGestureLockLayout.setDotCount(3);
    mGestureLockLayout.setMode(GestureLockLayout.VERIFY_MODE);
    //设置手势解锁最大尝试次数 默认 5
    mGestureLockLayout.setTryTimes(5);
    animation = AnimationUtils.loadAnimation(this, R.anim.shake);
    mGestureLockLayout.setOnLockVerifyListener(new OnLockVerifyListener() {
      @Override
      public void onGestureSelected(int id) {
        //每选中一个点时调用
      }

      @Override
      public void onGestureFinished(boolean isMatched) {
        //绘制手势解锁完成时调用
        if (isMatched) {
          if ("change".equals(type)) {
            startActivity(SetGestureLockActivity.class);
          } else if ("login".equals(type)) {
            startActivity(MainActivity.class);
          }
          finish();
        } else {
          hintTV.setVisibility(View.VISIBLE);
          mNumber = --mNumber;
          hintTV.setText("你还有" + mNumber + "次机会");
          hintTV.startAnimation(animation);
          mGestureLockLayout.startAnimation(animation);
          ToolUtils.setVibrate(mContext);
        }
        resetGesture();
      }

      @Override
      public void onGestureTryTimesBoundary() {
        //超出最大尝试次数时调用
        mGestureLockLayout.setTouchable(false);
      }
    });
  }

  /**
   * 重置手势布局(只是布局)
   */
  private void resetGesture() {
    new Handler().postDelayed(new Runnable() {
      @Override
      public void run() {
        mGestureLockLayout.resetGesture();
      }
    }, 300);
  }

}

项目地址:https://github.com/pengjunshan/LanguageFingerprintGesture

点击下方喜欢收藏,万一后期项目用得到呢
谢谢进入我的博客,请多多点评
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 好久没写文章了,最近也比较偷懒,今天继续讨论我实际开发中遇到的需求,那就是关于APP解锁,大家都知道。现在越来越多...
    青蛙要fly阅读 3,058评论 2 26
  • 最近项目中添加了一个新的登录方式:指纹识别。好巧不巧这块分给了我,然后一顿研究。下面想把一些实现的代码贴出来做个笔...
    SHERLOCKvv阅读 23,765评论 6 28
  • 搬家之难兮,难于上山巅。搬家之乱兮,诸事记心间。东市购家电,西市看床单。南市买麻袋,北市找车搬。大物用大箱,小件靠...
    风小扬阅读 373评论 6 5
  • 上一篇spring getBean 源码学习(上)基本上把getBean的过程细节了解清楚了,还剩下一些疑问以及没...
    jwfy阅读 2,482评论 0 0
  • 【日精进打卡第30天】 【知~学习】 《六项精进》2遍 共53遍 《大学》2遍 共54遍 【经典名句分享】 抱最大...
    斐然之夏阅读 143评论 0 0