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閱讀 229,619評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內容

  • 好久沒寫文章了,最近也比較偷懶,今天繼續討論我實際開發中遇到的需求,那就是關于APP解鎖,大家都知道。現在越來越多...
    青蛙要fly閱讀 3,107評論 2 26
  • 最近項目中添加了一個新的登錄方式:指紋識別。好巧不巧這塊分給了我,然后一頓研究。下面想把一些實現的代碼貼出來做個筆...
    SHERLOCKvv閱讀 24,008評論 6 28
  • 搬家之難兮,難于上山巔。搬家之亂兮,諸事記心間。東市購家電,西市看床單。南市買麻袋,北市找車搬。大物用大箱,小件靠...
    風小揚閱讀 408評論 6 5
  • 上一篇spring getBean 源碼學習(上)基本上把getBean的過程細節了解清楚了,還剩下一些疑問以及沒...
    jwfy閱讀 2,499評論 0 0
  • 【日精進打卡第30天】 【知~學習】 《六項精進》2遍 共53遍 《大學》2遍 共54遍 【經典名句分享】 抱最大...
    斐然之夏閱讀 149評論 0 0