其实九宫格解锁在Android上不是什么新鲜的东西,至今还有不少人的锁屏界面的解锁方式是九宫格解锁,但至于app九宫格解锁的要求,本人至今只使用支付宝的九宫格解锁
最近在做一个app的时候有一个需求,与支付宝有点类似 ,是牵涉到金钱支付的项目 所以有需求在app上加一个九宫格解锁(跟钱有关,小心为上或者说是避免其他人使用你的app),具体需求如下:
1、界面
具体说来可以归类为4类:splash界面、login界面、九宫格界面、主界面(包括应用的其它界面)
2、要求【程序从后台进入前台、手机熄屏后打开、点击app图标进入】
2.1、splash界面不显示解锁界面
2.2、登陆界面不显示解锁界面
2.3、其它界面需要显示解锁界面
2.4、当前app处于锁屏界面,推送消息来了,打开通知需要显示解锁界面,解锁后显示推送具体的页面,在该页面返回不需要显示解锁,直接显示以前的界面
2.5、app未启动,推送消息来时,显示解锁界面,解锁后显示推送界面,在推送界面返回,进入程序splash界面
2.6、程序第一次进入主界面时需要设置九宫格解锁,设置成功后即可进入主界面,后面只需要解锁
2.7、四次解锁失败后,程序被锁住60分钟,当然可以通过登陆界面直接登陆进入
2.8、其它地方暂定
由于Android的每个界面生命周期是单独控制的,所以统一监听程序后台进前台以及实现上面的逻辑有点晕,如下是我的实现代码,本人技术有限,代码肯定有一些逻辑误区以及不足处,请谅解
解锁画布【网上扒的,具体此处已经找不到了,自己也修改了一些】‘
package com.lockdemo.view;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* 自定义锁屏View
*/
public class GestureLockView extends View {
/** 解锁密码key */
private String key = "";
private OnGestureFinishListener onGestureFinishListener;
/** 解锁圆点数组 */
private LockCircle[] cycles;
/** 存储触碰圆的序列 */
private List<Integer> linedCycles = new ArrayList<Integer>();
// 画笔
/** 空心外圆 */
private Paint paintNormal;
/** 点击后内部圆 */
private Paint paintInnerCycle;
/** 画路径 */
private Paint paintLines;
private Path linePath = new Path();
/** 当前手指X,Y位置 */
private int eventX, eventY;
/** 能否操控界面绘画 */
private boolean canContinue = true;
/** 能否操控界面绘画 */
private boolean unlockable = true;
/** 设置密码模式 */
private boolean setlockmode = false;
/** 错误解锁的次数 */
public static int LockErrorTime = 5;
/** 验证结果 */
private boolean result;
private Timer timer;
/** 未选中颜色 */
private final int NORMAL_COLOR = Color.parseColor("#959BB4");
/** 错误颜色 */
private final int ERROE_COLOR = Color.parseColor("#FF2525"); // 正常外圆颜色
/** 选中时颜色 */
private final int TOUCH_COLOR = Color.parseColor("#409DE5"); // 选中内圆颜色
/** 设置时颜色 */
private final int SET_COLOR = Color.parseColor("#7CCD7C"); // 选中内圆颜色
// =================================start=构造方法========================
public GestureLockView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public GestureLockView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GestureLockView(Context context) {
this(context, null);
}
// ===============================end=构造方法========================
/** 初始化 */
public void init() {
paintNormal = new Paint();
paintNormal.setAntiAlias(true);
paintNormal.setStrokeWidth(5);
paintNormal.setStyle(Paint.Style.STROKE);
paintInnerCycle = new Paint();
paintInnerCycle.setAntiAlias(true);
paintInnerCycle.setStyle(Paint.Style.FILL);
paintLines = new Paint();
paintLines.setAntiAlias(true);
paintLines.setStyle(Paint.Style.STROKE);
paintLines.setStrokeWidth(10);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int spceSize = MeasureSpec.getSize(widthMeasureSpec);
heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (spceSize * 0.85 + 0.5f), specMode);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int perWidthSize = getWidth() / 7;
int perHeightSize = getHeight() / 6;
/** 初始化圆的参数 */
if (cycles == null && (perWidthSize > 0) && (perHeightSize > 0)) {
cycles = new LockCircle[9];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
LockCircle lockCircle = new LockCircle();
lockCircle.setNum(i * 3 + j);
lockCircle.setOx(perWidthSize * (j * 2 + 1.5f) + 0.5f);
lockCircle.setOy(perHeightSize * (i * 2 + 1) + 0.5f);
lockCircle.setR(perWidthSize * 0.6f);
cycles[i * 3 + j] = lockCircle;
}
}
}
}
public void setKey(String key) {
this.key = key;
}
public void setUnlock(boolean unlockable) {
this.unlockable = unlockable;
}
public void setLockMode(boolean setlockmode) {
this.setlockmode = setlockmode;
}
public void setOnGestureFinishListener(OnGestureFinishListener onGestureFinishListener) {
this.onGestureFinishListener = onGestureFinishListener;
}
/** 手势输入完成后回调接口 */
public interface OnGestureFinishListener {
/** 手势输入完成后回调函数 */
public void OnGestureFinish(boolean success, String key);
}
/** 监听手势 */
@Override
public boolean onTouchEvent(MotionEvent event) {
if (canContinue && unlockable) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
eventX = (int) event.getX();
eventY = (int) event.getY();
for (int i = 0; i < cycles.length; i++) {
if (cycles[i].isPointIn(eventX, eventY)) {
cycles[i].setOnTouch(true);
if (!linedCycles.contains(cycles[i].getNum())) {
linedCycles.add(cycles[i].getNum());
}
}
}
break;
case MotionEvent.ACTION_UP:
// 手指离开暂停触碰
canContinue = false;
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < linedCycles.size(); i++) {
stringBuffer.append(linedCycles.get(i));
}
result = key.equals(stringBuffer.toString());
if (onGestureFinishListener != null && linedCycles.size() > 0) {
onGestureFinishListener.OnGestureFinish(result, stringBuffer.toString());
}
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
eventX = eventY = 0;
for (int i = 0; i < 9; i++) {
cycles[i].setOnTouch(false);
}
linedCycles.clear();
linePath.reset();
canContinue = true;
postInvalidate();// 在非ui线程刷新界面
}
}, 1000);
break;
}
invalidate();
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int cycleSize = cycles.length;
for (int i = 0; i < cycleSize; i++) {
// 画完并且错误
if (!canContinue && !result) {
if (cycles[i].isOnTouch()) {
if (setlockmode) {
drawInnerCycle(cycles[i], canvas, SET_COLOR);
drawOutsideCycle(cycles[i], canvas, SET_COLOR);
} else {
drawInnerCycle(cycles[i], canvas, ERROE_COLOR);
drawOutsideCycle(cycles[i], canvas, ERROE_COLOR);
}
} else
drawOutsideCycle(cycles[i], canvas, NORMAL_COLOR);
}
// 绘画中
else {
if (cycles[i].isOnTouch()) {
drawInnerCycle(cycles[i], canvas, TOUCH_COLOR);
drawOutsideCycle(cycles[i], canvas, TOUCH_COLOR);
} else
drawOutsideCycle(cycles[i], canvas, NORMAL_COLOR);
}
}
if (!canContinue && !result) {
if (setlockmode) {
drawLine(canvas, SET_COLOR);
} else {
drawLine(canvas, ERROE_COLOR);
}
} else {
drawLine(canvas, TOUCH_COLOR);
}
}
/** 画空心圆 */
private void drawOutsideCycle(LockCircle lockCircle, Canvas canvas, int color) {
paintNormal.setColor(color);
canvas.drawCircle(lockCircle.getOx(), lockCircle.getOy(), lockCircle.getR(), paintNormal);
}
/** 画横线 */
private void drawLine(Canvas canvas, int color) {
// 构建路径
linePath.reset();
if (linedCycles.size() > 0) {
int size = linedCycles.size();
for (int i = 0; i < size; i++) {
int index = linedCycles.get(i);
float x = cycles[index].getOx();
float y = cycles[index].getOy();
if (i == 0) {
linePath.moveTo(x, y);
} else {
linePath.lineTo(x, y);
}
}
if (canContinue) {
linePath.lineTo(eventX, eventY);
} else {
linePath.lineTo(cycles[linedCycles.get(linedCycles.size() - 1)].getOx(), cycles[linedCycles.get(linedCycles.size() - 1)].getOy());
}
paintLines.setColor(color);
canvas.drawPath(linePath, paintLines);
}
}
/** 画中心圆圆 */
private void drawInnerCycle(LockCircle myCycle, Canvas canvas, int color) {
paintInnerCycle.setColor(color);
canvas.drawCircle(myCycle.getOx(), myCycle.getOy(), myCycle.getR() / 3f, paintInnerCycle);
}
/**
* 每个圆点类
*/
class LockCircle {
/** 圆心横坐标 */
private float ox;
/** 圆心纵坐标 */
private float oy;
/** 半径长度 */
private float r;
/** 代表数值 */
private Integer num;
/** 是否选择:false=未选中 */
private boolean onTouch;
public float getOx() {
return ox;
}
public void setOx(float ox) {
this.ox = ox;
}
public float getOy() {
return oy;
}
public void setOy(float oy) {
this.oy = oy;
}
public void setOy(int oy) {
this.oy = oy;
}
public float getR() {
return r;
}
public void setR(float r) {
this.r = r;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public boolean isOnTouch() {
return onTouch;
}
public void setOnTouch(boolean onTouch) {
this.onTouch = onTouch;
}
/** 判读传入位置是否在圆心内部 */
public boolean isPointIn(int x, int y) {
double distance = Math.sqrt((x - ox) * (x - ox) + (y - oy) * (y - oy));
return distance < r;
}
}
public void minusErrorTime() {
LockErrorTime--;
}
public int getLockErrorTime() {
return LockErrorTime;
}
public void resetErrorTime() {
LockErrorTime = 5;
}
}
package com.lockdemo.activity;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.util.PreferencesUtils;
import com.lidroid.xutils.util.StringUtils;
import com.lidroid.xutils.view.annotation.ViewInject;
import com.lockdemo.application.LockApplication;
import com.lockdemo.view.GestureLockView;
import com.lockdemo.view.GestureLockView.OnGestureFinishListener;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.text.Html;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.TextView;
public class LockActivity extends BaseActivity {
@ViewInject(R.id.gesture_tv_tips)
private TextView gesture_tv_tips;
@ViewInject(R.id.gestureLockView)
private GestureLockView gestureLockView;
@ViewInject(R.id.gesture_tv_forget)
private TextView gesture_tv_forget;
@ViewInject(R.id.gesture_btn_reset)
private TextView gesture_btn_reset;
private Animation animation;
private boolean flag = false;
private String key;
private String keystore;
private int count = 0;
private MyCount counttime;
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.activity_lock);
LockApplication.getInstance().addActivity(this);
ViewUtils.inject(this);
init();
}
@Override
protected void onDestroy() {
super.onDestroy();
gestureLockView.resetErrorTime();
stopCountTime();
LockApplication.isShowLock = false;
}
@Override
protected void onResume() {
super.onResume();
LockApplication.isShowLock = true;
}
private void init() {
animation = new TranslateAnimation(-20, 20, 0, 0);
animation.setDuration(50);
animation.setRepeatCount(2);
animation.setRepeatMode(Animation.REVERSE);
key = PreferencesUtils.getString(LockActivity.this, PreferencesUtils.getString(LockActivity.this, "UserUid") + "_Lockkey", "");
if (!StringUtils.isEmpty(key)) {
flag = true;
// 设置密码
gestureLockView.setKey(key);
startCountTime(countTimeBollean());
} else {
gesture_tv_tips.setTextColor(Color.parseColor("#FFFFFF"));
gesture_tv_tips.setVisibility(View.VISIBLE);
gesture_tv_tips.setText("请先设置应用解锁密码\n[至少4位]");
gesture_tv_forget.setVisibility(View.GONE);
gestureLockView.setLockMode(true);
}
// 手势完成后回调
gestureLockView.setOnGestureFinishListener(new OnGestureFinishListener() {
@Override
public void OnGestureFinish(boolean success, String key) {
if (flag) {
if (success) {
LockApplication.getInstance().toFinish(LockActivity.class);
finish();
} else {
gestureLockView.minusErrorTime();
if (gestureLockView.getLockErrorTime() > 0) {
gesture_tv_tips.setTextColor(Color.parseColor("#FF2525"));
gesture_tv_tips.setVisibility(View.VISIBLE);
gesture_tv_tips.setText("密码错误\n还可以再解锁" + gestureLockView.getLockErrorTime() + "次");
gesture_tv_tips.startAnimation(animation);
} else {
setLockTime(System.currentTimeMillis());
startCountTime((long) 3600000);
}
}
} else {
if (key.length() > 3) {
count++;
if (count == 1) {
keystore = key;
gesture_tv_tips.setTextColor(Color.parseColor("#FFFFFF"));
gesture_tv_tips.setText("请再次设置解锁密码");
gesture_btn_reset.setText(Html.fromHtml("<u>" + "重新设置" + "</u>"));
gesture_btn_reset.setVisibility(View.VISIBLE);
} else if (count == 2) {
if (keystore.equals(key)) {
showToast("解锁密码设置成功");
// 此处密码请自行加密
PreferencesUtils.putString(LockActivity.this, PreferencesUtils.getString(LockActivity.this, "UserUid") + "_Lockkey", keystore);
LockApplication.getInstance().toFinish(LockActivity.class);
finish();
} else {
count = 0;
gesture_tv_tips.setText("请先设置应用解锁密码\n[至少4位]");
gesture_tv_tips.startAnimation(animation);
showToast("两次密码设置不一致,请重新设置");
gesture_btn_reset.setVisibility(View.GONE);
}
}
} else {
if (count == 1) {
count = 0;
gesture_tv_tips.setText("请先设置应用解锁密码\n[至少4位]");
gesture_tv_tips.startAnimation(animation);
showToast("两次密码设置不一致,请重新设置");
gesture_btn_reset.setVisibility(View.GONE);
} else {
count = 0;
gesture_tv_tips.setText("请先设置应用解锁密码\n[至少4位]");
gesture_tv_tips.startAnimation(animation);
showToast("解锁密码太短,请重新设置");
}
}
}
}
});
gesture_tv_forget.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(LockActivity.this, LoginActivity.class);
startActivity(intent);
finish();
}
});
gesture_btn_reset.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
keystore = null;
count = 0;
gesture_tv_tips.setText("请先设置应用解锁密码\n[至少4位]");
gesture_tv_tips.startAnimation(animation);
gesture_btn_reset.setVisibility(View.GONE);
}
});
}
/**
* 开始计时工作
*/
private void startCountTime(Long count) {
if (count != 0) {
gestureLockView.setUnlock(false);
gesture_tv_tips.setTextColor(Color.parseColor("#ED6C00"));
gesture_tv_tips.setVisibility(View.VISIBLE);
counttime = new MyCount(count, 1000);
counttime.start();
}
}
/**
* 停止计时
*/
private void stopCountTime() {
if (counttime != null) {
counttime.cancel();
counttime = null;
}
}
/**
*
* @Title: countTimeBollean
* @Description: TODO(计算解锁时间)
* @param @return 设定文件
* @return long 返回类型
* @throws
*/
private long countTimeBollean() {
long cnt = PreferencesUtils.getLong(LockActivity.this, PreferencesUtils.getString(LockActivity.this, "UserUid") + "_DataNum", 0);
if (cnt == 0) {
return 0;
} else {
long spass = System.currentTimeMillis() - cnt;
if (spass < 0 || spass > 3600000) {
return 0;
} else {
return (3600000 - spass);
}
}
}
private class MyCount extends CountDownTimer {
public MyCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onFinish() {
gestureLockView.setUnlock(true);
gestureLockView.resetErrorTime();
gesture_tv_tips.setTextColor(Color.parseColor("#FFFFFF"));
gesture_tv_tips.setText("请解锁应用");
setLockTime(0);
}
@Override
public void onTick(long millisUntilFinished) {
long minutes = millisUntilFinished / (60 * 1000);
long seconds = (millisUntilFinished % (60 * 1000)) / 1000;
if (minutes > 0) {
if (seconds > 0) {
gesture_tv_tips.setText("请" + minutes + "分" + seconds + "秒后尝试解锁");
} else {
gesture_tv_tips.setText("请" + minutes + "分后尝试解锁");
}
} else {
gesture_tv_tips.setText("请" + seconds + "秒后尝试解锁");
}
}
}
private void setLockTime(long time) {
PreferencesUtils.putLong(LockActivity.this, PreferencesUtils.getString(LockActivity.this, "UserUid") + "_DataNum", time);
}
private void back() {
LockApplication.getInstance().exit();
}
@Override
public void onBackPressed() {
back();
}
}
LockApplication:
package com.lockdemo.application;
import java.util.ArrayList;
import java.util.List;
import com.lockdemo.recall.ScreenListener;
import android.app.Activity;
import android.app.Application;
public class LockApplication extends Application {
public static LockApplication instance = null;
/** 程序活动列表 */
private List<Activity> activityList = new ArrayList<Activity>();
public static LockApplication getInstance() {
if (instance == null) {
instance = new LockApplication();
}
return instance;
}
@Override
public void onCreate() {
super.onCreate();
}
/** 程序在前台 */
public static boolean isActive = false;
/** 程序从后台到前台 */
public static boolean isBtoF = false;
/** 正在显示lock界面 */
public static boolean isShowLock = false;
/** 不显示显示lock界面【splash界面和login界面】 */
public static boolean notShowLock = false;
/** 显示亮屏的次数 */
public static int ligthCount = 0;
/** main界面resume次数 */
public static int mainCount = 0;
/** 屏幕锁屏监听 */
public static ScreenListener sl;
public void addActivity(Activity activity) {
activityList.add(activity);
}
public void exit() {
sl.unregisterListener();
for (Activity activity : activityList) {
activity.finish();
}
System.exit(0);
}
public void finishAll() {
for (Activity activity : activityList) {
activity.finish();
}
activityList.clear();
}
@SuppressWarnings("rawtypes")
public void finishOthers(Class clazz) {
for (int i = 0; i < activityList.size(); i++) {
if (!clazz.getName().equals(activityList.get(i).getClass().getName())) {
activityList.get(i).finish();
activityList.remove(i);
}
}
}
@SuppressWarnings("rawtypes")
public void toFinish(Class clazz) {
for (int i = 0; i < activityList.size(); i++) {
if (clazz.getName().equals(activityList.get(i).getClass().getName())) {
activityList.get(i).finish();
activityList.remove(i);
}
}
}
@SuppressWarnings("rawtypes")
public boolean hasClazz(Class clazz) {
boolean flag = false;
for (int i = 0; i < activityList.size(); i++) {
if (clazz.getName().equals(activityList.get(i).getClass().getName())) {
flag = true;
}
}
return flag;
}
}
Activity基类:
package com.lockdemo.activity;
import java.util.List;
import com.lidroid.xutils.util.LogUtils;
import com.lockdemo.application.LockApplication;
import com.lockdemo.recall.ScreenListener;
import com.lockdemo.recall.ScreenListener.ScreenStateListener;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.widget.Toast;
public class BaseActivity extends FragmentActivity {
private final static int TOASTTIME = 1000;
private Intent intent = null;
@Override
public void onBackPressed() {
super.onBackPressed();
}
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (!LockApplication.isActive) {
// app 从后台唤醒,进入前台
LockApplication.isActive = true;
if (LockApplication.isBtoF) {
LockApplication.isBtoF = false;
if (!LockApplication.isShowLock && !LockApplication.notShowLock) {
intent = new Intent(getApplicationContext(), LockActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
}
}
if (LockApplication.sl == null) {
LockApplication.sl = new ScreenListener(this);
LockApplication.sl.begin(new ScreenStateListener() {
@Override
public void onUserPresent() {
LogUtils.d("onUserPresent");
}
@Override
public void onScreenOn() {
LogUtils.d("onScreenOn");
/** 排除第一次监听 显示亮屏 */
LockApplication.ligthCount++;
if (LockApplication.isActive && !LockApplication.isShowLock && (LockApplication.ligthCount > 1) && !LockApplication.notShowLock) {
intent = new Intent(getApplicationContext(), LockActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
}
@Override
public void onScreenOff() {
LogUtils.d("onScreenOff");
}
});
}
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
if (!isAppOnForeground()) {
// app 进入后台
LockApplication.isActive = false;
LockApplication.isBtoF = true;
}
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
super.startActivityForResult(intent, requestCode);
}
@Override
protected void onRestart() {
super.onRestart();
}
public void showToast(String msg) {
Toast.makeText(getApplicationContext(), msg, TOASTTIME).show();
}
/**
* 程序是否在前台运行
*
* @return
*/
public boolean isAppOnForeground() {
ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
String packageName = getApplicationContext().getPackageName();
List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses == null)
return false;
for (RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(packageName) && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
return false;
}
}