手势view 默认九宫格 使用时应注册回调,会返回路径。该自定义view最关键的算法是能检测是否能补全跨越点位,能有提高解锁成功率
解锁保护的关键是使用Application注册一个activity的监听器,在onActivityResume去处理是否要拉起解锁保护页
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.xxx.CommonUtil;
/**
* @PackageName : com.xxx.lock
* @Time : 5/11/23 11:49 AM
* @Description :
*/
public class LockView extends View {
public static int MINSELECTED = 4;
private final static String TAG = "AlipayLockView";
public interface OnLockInputListener {
void onLockDone(String path);
void onLockInput(int indexUnlocked);
}
public interface OnFirstInputListener {
void onFirstInput();
}
private Paint mPaint;
private int iCount = 9;
private int iCountPerLine = 3;
private int iPointBit = 0;
private int[] iPointArray;
private int iCurCount;
private boolean iDrawLineNeeded = false;
private boolean iDone = false;
private Point iCurPointerPoint = new Point();
private OnLockInputListener mOnLockInputListener = null;
private OnFirstInputListener mOnFirstInputListener = null;
private Drawable mGridFocused = null;
private Drawable mGridNormal = null;
private Drawable mGridError = null;
float mGridMargin = 0;
float mGridWidth = 0;
float mGridHeight = 0;
float mGridBetweenX = 0;
float mGridBetweenY = 0;
private float mGridRadius = 0;
private boolean mDensityLow = false;
private boolean isFirstInput = true;
//手势轨迹是否隐藏
private boolean isHideOrbit = false;
//手势验证是否错误
private boolean isCheckError = false;
//是否为设置手势
private boolean isSetGesture = false;
public AlipayLockView(Context context) {
this(context, null);
}
public AlipayLockView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
// TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LockView);
// mGridFocused = a.getDrawable(R.styleable.LockView_gridFocused);
// mGridNormal = a.getDrawable(R.styleable.LockView_gridNormal);
// mGridError = a.getDrawable(R.styleable.LockView_gridError);
// a.recycle();
init();
}
public void setOnLockInputListener(OnLockInputListener l) {
mOnLockInputListener = l;
}
public void setOnFirstInputListener(OnFirstInputListener l) {
mOnFirstInputListener = l;
}
public void clear() {
iPointBit = 0;
iDone = false;
isFirstInput = true;
isCheckError = false;
if (0 != iCurCount) {
iCurCount = 0;
this.invalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = false;
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
iDone = true;
if (null != mOnLockInputListener) {
String path = "";
for (int i = 0; i < iCurCount; i++) {
path += iPointArray[i];
}
if (path.length() > 0) {
mOnLockInputListener.onLockDone(path);
}
}
iDrawLineNeeded = false;
this.invalidate();
result = true;
break;
case MotionEvent.ACTION_DOWN:
isCheckError = false;
if (iDone) {
iPointBit = 0;
isFirstInput = true;
iCurCount = 0;
iDone = false;
}
result = checkDrawNeeded(event.getX(), event.getY());
if (result) {
this.invalidate();
}
break;
case MotionEvent.ACTION_CANCEL:
result = true;
break;
case MotionEvent.ACTION_MOVE:
iDrawLineNeeded = false;
final int historySize = event.getHistorySize();
for (int i = 0; i < historySize + 1; i++) {
final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
iDrawLineNeeded |= checkDrawNeeded(x, y);
}
if (iDrawLineNeeded) {
iCurPointerPoint.x = (int) event.getX();
iCurPointerPoint.y = (int) event.getY();
this.invalidate();
}
break;
default:
result = true;
break;
}
return true;//super.onTouchEvent(event);
}
private Paint mPaintNormal;
private Paint mPaintError;
private Paint mPaintFocus;
private void init() {
mPaintNormal = new Paint();
mPaintError = new Paint();
mPaintFocus = new Paint();
mPaint = new Paint();
mPaint.setAntiAlias(true);
isFirstInput = true;
iPointArray = new int[iCount];
for (int i = 0; i < iCount; i++) {
iPointArray[i] = -1;
}
iCurCount = 0;
DisplayMetrics dmDisplayMetrics = getResources().getDisplayMetrics();
if (DisplayMetrics.DENSITY_HIGH > dmDisplayMetrics.densityDpi || 728 == dmDisplayMetrics.heightPixels) {
mDensityLow = true;
mPaint.setStrokeWidth(CommonUtil.dp2Px(getContext(), 2));
}
//meizu
else if (960 == dmDisplayMetrics.heightPixels && 640 == dmDisplayMetrics.widthPixels) {
mDensityLow = true;
mPaint.setStrokeWidth(CommonUtil.dp2Px(getContext(), 4));
} else {
mPaint.setStrokeWidth(CommonUtil.dp2Px(getContext(), 4));
}
}
private float gridWidth;
private float gridHeight;
private float gridMargin;
private float gridRadius;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
mGridWidth = mGridHeight = width / (2f * iCountPerLine - 1);
mGridMargin = (width - mGridWidth * iCountPerLine) / (2 * iCountPerLine);
mGridBetweenX = mGridWidth + 2 * mGridMargin;
mGridBetweenY = mGridHeight + (mDensityLow ? 1 : 2) * mGridMargin;
mGridRadius = mGridWidth / 2;
float height = mGridBetweenY * (iCountPerLine - 1) + mGridHeight + (mDensityLow ? 0 : 2) * mGridMargin;
setMeasuredDimension(width, (int) height);
//适配新视觉
gridWidth = gridHeight = Math.min(mGridWidth, CommonUtil.dp2Px(getContext(), 60));
gridMargin = (mGridWidth - gridWidth) / 2;
gridRadius = gridWidth / 2;
Log.i("yglOnMeasure", "onMeasure mGridWidth = " + mGridWidth
+ " 60dp = " + CommonUtil.dp2Px(getContext(), 60)
+ " mGridMargin = " + mGridMargin
+ " mGridBetweenX = " + mGridBetweenX
+ " mGridBetweenY = " + mGridBetweenY
+ " mGridRadius = " + mGridRadius
+ " height = " + height
+ " gridWidth = " + gridWidth
+ " gridMargin = " + gridMargin
+ " gridRadius = " + gridRadius);
// mGridFocused.setBounds(gridMargin, gridMargin, gridMargin + gridWidth, gridMargin + gridWidth);
// mGridNormal.setBounds(gridMargin, gridMargin, gridMargin + gridWidth, gridMargin + gridWidth);
// mGridError.setBounds(gridMargin, gridMargin, gridMargin + gridWidth, gridMargin + gridWidth);
}
//检测是不是要主动补全
private boolean checkDrawNeeded(float x, float y) {
for (int i = 0; i < iCount; i++) {
if (isAvailable(i)) {
if (CheckInGrid(x, y, i)) {
if (iCurCount > 0) {
int indexMissing = detectCircleMissing(iPointArray[iCurCount - 1], i);
if (-1 != indexMissing) {
hit(indexMissing);
}
}
hit(i);
break;
}
}
}
return iCurCount > 0;
}
private void hit(int index) {
iPointArray[iCurCount++] = index;
iPointBit |= (1 << (index + 1));
}
private boolean isAvailable(int index) {
return 0 == (iPointBit & (1 << (index + 1)));
}
//检测补全算法
private int detectCircleMissing(int indexBefore, int curIndex) {
final int xBefore = indexBefore % 3;
final int yBefore = indexBefore / 3;
final int xCur = curIndex % 3;
final int yCur = curIndex / 3;
if (0 == (xBefore - xCur) % 2 &&
0 == (yBefore - yCur) % 2) {
int tempIndex = (indexBefore + curIndex) / 2;
if (isAvailable(tempIndex)) {
return tempIndex;
}
}
return -1;
}
private boolean CheckInGrid(float x, float y, int index) {
float xPoint = mGridMargin + mGridBetweenX * (index % iCountPerLine) + mGridWidth / 2;
float yPoint = (mDensityLow ? 0 : mGridMargin) + mGridBetweenY * (index / iCountPerLine) + mGridWidth / 2;
int deltaX = (int) (xPoint - x);
int deltaY = (int) (yPoint - y);
double deltaR = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (mDensityLow) {
return deltaR <= mGridRadius;
} else {
return deltaR <= mGridRadius + CommonUtil.dp2Px(getContext(), 8);
}
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < iCount; i++) {
drawGrid(canvas, i);
}
for (int i = 0; i < iCurCount - 1; i++) {
if (isCheckError) {
mPaint.setColor(Color.parseColor("#FF3141"));
}
canvas.drawLine(GetGridX(iPointArray[i]), GetGridY(iPointArray[i]),
GetGridX(iPointArray[i + 1]), GetGridY(iPointArray[i + 1]), mPaint);
}
if (iDrawLineNeeded && iCurCount > 0) {
if (isHideOrbit) {
mPaint.setColor(Color.argb(0, 0, 0, 0));
} else {
mPaint.setColor(Color.parseColor("#1677ff"));
}
canvas.drawLine(GetGridX(iPointArray[iCurCount - 1]), GetGridY(iPointArray[iCurCount - 1]),
iCurPointerPoint.x, iCurPointerPoint.y, mPaint);
}
super.onDraw(canvas);
}
private int GetGridX(int index) {
return (int)(mGridMargin + mGridBetweenX * (index % iCountPerLine) + mGridRadius);
}
private int GetGridY(int index) {
return (int)((mDensityLow ? 0 : mGridMargin) + mGridBetweenY * (index / iCountPerLine) + mGridRadius);
}
private void drawGrid(Canvas canvas, int index) {
float x = mGridMargin + mGridBetweenX * (index % iCountPerLine);
float y = (mDensityLow ? 0 : mGridMargin) + mGridBetweenY * (index / iCountPerLine);
int occupied = iPointBit & (1 << (index + 1));
canvas.save();
canvas.translate(x, y);
if (occupied > 0) {
if (isHideOrbit) {
if (isCheckError) {
// mGridError.draw(canvas);
drawIcon(error, canvas);
} else {
// mGridNormal.draw(canvas);
drawIcon(normal, canvas);
}
} else {
if (isCheckError) {
// mGridError.draw(canvas);
drawIcon(error, canvas);
} else {
// mGridFocused.draw(canvas);
drawIcon(focus, canvas);
}
}
//首次点击9个元圈中第一个时触发
if (isFirstInput && mOnFirstInputListener != null) {
isFirstInput = false;
mOnFirstInputListener.onFirstInput();
}
} else {
// mGridNormal.draw(canvas);
drawIcon(normal, canvas);
}
canvas.restore();
//canvas.drawCircle(x, y, iPointSmallRadius, mPaint);
}
private final static int normal = 1; //内 #CBCBCB/#3E3E3E 外 #ECECEC/#262626
private final static int focus = 2; //内 COLOR_BRAND1 外COLOR_WATHET #E7F1FF/#0D2543 浅蓝色(按钮)
private final static int error = 3; //内 COLOR_RED FF3141/FF4A58 外 #FEDCDF/#391517
private RectF oval;
private void drawIcon(int tag, Canvas canvas) {
if (null == oval) {
oval = new RectF();
oval.left = (gridMargin);
oval.top = (gridMargin);
oval.right = gridMargin + gridWidth;
oval.bottom = gridMargin + gridHeight;
}
if (tag == normal) {
//画整圆弧
mPaintNormal.setAntiAlias(true);//防锯齿
mPaintNormal.setColor(Color.parseColor("#ECECEC"));
mPaintNormal.setStyle(Paint.Style.STROKE);
mPaintNormal.setStrokeWidth(CommonUtil.dp2Px(getContext(), 1));
canvas.drawArc(oval, 0, 360, false, mPaintNormal);
mPaintNormal.reset();
//画圆
//画圆画笔设置
mPaintNormal.setAntiAlias(true);//防锯齿
mPaintNormal.setColor(Color.parseColor("#CBCBCB"));
mPaintNormal.setStyle(Paint.Style.FILL);
canvas.drawCircle(mGridWidth / 2f, mGridHeight / 2f, gridRadius * 0.46f, mPaintNormal);
mPaintNormal.reset();
} else if (tag == focus) {
//画整圆弧
mPaintFocus.setAntiAlias(true);//防锯齿
mPaintFocus.setColor(Color.parseColor("#E7F1FF"));
mPaintFocus.setStyle(Paint.Style.FILL_AND_STROKE);
mPaintFocus.setStrokeWidth(CommonUtil.dp2Px(getContext(), 1));
canvas.drawArc(oval, 0, 360, true, mPaintFocus);
mPaintFocus.reset();
mPaintFocus.setAntiAlias(true);//防锯齿
mPaintFocus.setColor(Color.parseColor("#1677ff"));
mPaintFocus.setStyle(Paint.Style.FILL);
canvas.drawCircle(mGridWidth / 2f, mGridHeight / 2f, gridRadius * 0.46f, mPaintFocus);
mPaintFocus.reset();
} else if (tag == error) {
//画整圆弧
mPaintError.setAntiAlias(true);//防锯齿
mPaintError.setColor(Color.parseColor("#FEDCDF"));
mPaintError.setStyle(Paint.Style.FILL_AND_STROKE);
mPaintError.setStrokeWidth(CommonUtil.dp2Px(getContext(), 1));
canvas.drawArc(oval, 0, 360, true, mPaintError);
mPaintError.reset();
mPaintError.setAntiAlias(true);//防锯齿
mPaintError.setColor(Color.parseColor("#FF3141"));
mPaintError.setStyle(Paint.Style.FILL);
canvas.drawCircle(mGridWidth / 2f, mGridHeight / 2f, gridRadius * 0.46f, mPaintError);
mPaintError.reset();
}
}
public void setIsHideOrbit(boolean isHideOrbit) {
this.isHideOrbit = isHideOrbit;
}
public void setIsCheckError(boolean isCheckError) {
this.isCheckError = isCheckError;
}
public boolean getIsCheckError() {
return this.isCheckError;
}
public void setIsSetGesture(boolean isSetGesture) {
this.isSetGesture = isSetGesture;
}
}
util
public class CommonUtil {
private final static String TAG = "CommonUtils";
private static final int MIN_DELAY_TIME = 500;
private static long sLastClickTime;
private static final int UPLOADING_MIN_DELAY_TIME = 50;
private static long sLastUploadTime;
public static void performAddView(ViewGroup root, View child) {
try {
if (root == null || child == null) {
return;
}
ViewGroup parent = (ViewGroup) child.getParent();
if (parent != null) {
parent.removeView(child);
}
root.addView(child);
} catch (Exception e) {
Log.e(TAG, "performAddView e" + e);
}
}
public static int dp2Px(Context context, float dp) {
int px = 0;
if (context == null) return px;
try {
final float scale = context.getResources().getDisplayMetrics().density;
px = (int) (dp * scale + 0.5f);
} catch (Throwable e) {
Log.e("CommonUtil", "dp2px e", e);
}
return px;
}
public static boolean isFastClick() {
long currentClickTime = System.currentTimeMillis();
boolean isFastClick = (currentClickTime - sLastClickTime) <= MIN_DELAY_TIME;
Log.e(TAG, "log_common_FastClickUtil : " + (currentClickTime - sLastClickTime));
sLastClickTime = currentClickTime;
return isFastClick;
}
public static boolean isReUpload() {
long currentUploadTime = System.currentTimeMillis();
boolean isReLoad = (currentUploadTime - sLastUploadTime) <= UPLOADING_MIN_DELAY_TIME;
Log.e(TAG, "log_common_ReUploadUtil : " + (currentUploadTime - sLastUploadTime));
sLastUploadTime = currentUploadTime;
return isReLoad;
}
/**
* 判断是否为平板
*
* @return
*/
public static boolean isPadInches(Context context) {
try {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
double x = Math.pow(dm.widthPixels / dm.xdpi, 2);
double y = Math.pow(dm.heightPixels / dm.ydpi, 2);
//屏幕尺寸
double screenInches = Math.sqrt(x + y);
//大于7尺寸则为Pad
Log.e(TAG, "isPadInches = " + screenInches);
return screenInches >= 7;
} catch (Exception e) {
Log.e(TAG, "isPadInches e ", e);
}
return false;
}
public static boolean isPadGoogle(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
/**
* 获取已经安装app的信息列表
*
* @param context
* @return
*/
public static ArrayList<HashMap<String, Object>> getItems(Context context) {
PackageManager pckMan = context.getPackageManager();
ArrayList<HashMap<String, Object>> items = new ArrayList<HashMap<String, Object>>();
List<PackageInfo> packageInfo = pckMan.getInstalledPackages(0);
for (PackageInfo pInfo : packageInfo) {
HashMap<String, Object> item = new HashMap<String, Object>();
item.put("appimage", pInfo.applicationInfo.loadIcon(pckMan));
item.put("packageName", pInfo.packageName);
item.put("versionCode", pInfo.versionCode);
item.put("versionName", pInfo.versionName);
item.put("appName", pInfo.applicationInfo.loadLabel(pckMan).toString());
items.add(item);
// Log.i("ygl", " packageName: " + pInfo.packageName + " ,versionCode: " + pInfo.versionCode +
// " ,versionName: " + pInfo.versionName + " ,appName: " + pInfo.applicationInfo.loadLabel(pckMan).toString());
System.out.println("ygl packageName: " + pInfo.packageName + " ,versionCode: " + pInfo.versionCode +
" ,versionName: " + pInfo.versionName + " ,appName: " + pInfo.applicationInfo.loadLabel(pckMan).toString());
}
return items;
}
/**
* 获取指定app的版本code码
*
* @param context
* @param packageName
* @return
*/
public static int getVersionCode(Context context, String packageName) {
PackageManager manager = context.getPackageManager();
int code = 0;
try {
PackageInfo info = manager.getPackageInfo(packageName, 0);
code = info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return code;
}
/**
* 获取指定app的版本号
*
* @param context
* @param packageName
* @return
*/
public static String getVersionName(Context context, String packageName) {
PackageManager manager = context.getPackageManager();
String name = null;
try {
PackageInfo info = manager.getPackageInfo(packageName, 0);
name = info.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return name;
}
public static boolean isDarkNotificationTheme(Context context) {
return !isSimilarColor(Color.BLACK, getNotificationColor(context));
}
/**
* 获取通知栏颜色
*
* @param context
* @return
*/
public static int getNotificationColor(Context context) {
DownloadNotificationFactory.DefaultNotification defaultNotification = new DownloadNotificationFactory.DefaultNotification(context);
int layoutId = defaultNotification.getNotification().bigContentView.getLayoutId();
ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(context).inflate(layoutId, null, false);
if (viewGroup.findViewById(android.R.id.title) != null) {
return ((TextView) viewGroup.findViewById(android.R.id.title)).getCurrentTextColor();
}
return findColor(viewGroup);
}
/**
* @param baseColor
* @param color
* @return
*/
private static boolean isSimilarColor(int baseColor, int color) {
int simpleBaseColor = baseColor | 0xff000000;
int simpleColor = color | 0xff000000;
int baseRed = Color.red(simpleBaseColor) - Color.red(simpleColor);
int baseGreen = Color.green(simpleBaseColor) - Color.green(simpleColor);
int baseBlue = Color.blue(simpleBaseColor) - Color.blue(simpleColor);
double value = Math.sqrt(baseRed * baseRed + baseGreen * baseGreen + baseBlue * baseBlue);
if (value < 180.0) {
return true;
}
return false;
}
/**
* 获取颜色
*
* @param viewGroupSource
* @return
*/
private static int findColor(ViewGroup viewGroupSource) {
int color = Color.TRANSPARENT;
LinkedList<ViewGroup> viewGroups = new LinkedList<>();
viewGroups.add(viewGroupSource);
while (viewGroups.size() > 0) {
ViewGroup viewGroup1 = viewGroups.getFirst();
for (int i = 0; i < viewGroup1.getChildCount(); i++) {
if (viewGroup1.getChildAt(i) instanceof ViewGroup) {
viewGroups.add((ViewGroup) viewGroup1.getChildAt(i));
} else if (viewGroup1.getChildAt(i) instanceof TextView) {
if (((TextView) viewGroup1.getChildAt(i)).getCurrentTextColor() != -1) {
color = ((TextView) viewGroup1.getChildAt(i)).getCurrentTextColor();
}
}
}
viewGroups.remove(viewGroup1);
}
return color;
}
/**
* 获取资源Id
*
* @param context
* @param type
* @param name
* @return
*/
public static int getResourceId(Context context, String type, String name) {
try {
Class<?> clazz = Class.forName(context.getApplicationContext().getPackageName() + ".R$" + type);
Field field = clazz.getDeclaredField(name);
return (Integer) field.get(null);
} catch (Exception globalException) {
return -1;
}
}
/**
* 获取bitmap
*
* @param res
* @param resId
* @return
*/
public static Bitmap getBitmapFromResId(Resources res, int resId) {
return getBitmapFromDrawable(res.getDrawable(resId));
}
/**
* 获取bitmap
*
* @param d
* @return
*/
public static Bitmap getBitmapFromDrawable(Drawable d) {
if (d instanceof BitmapDrawable) return ((BitmapDrawable) d).getBitmap();
Bitmap bm = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
d.setBounds(0, 0, c.getWidth(), c.getHeight());
d.draw(c);
return bm;
}
}
在布局中使用 xml文件
<RelativeLayout
android:id="@+id/rl_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/select_skin_from_center"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="50dp">
<com.xxx.lock.LockView
android:id="@+id/pattern_lock_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:gravity="center_horizontal"
android:padding="12dp"
component:gridError="@drawable/gesture_pattern_error"
component:gridFocused="@drawable/gesture_pattern_selected"
component:gridNormal="@drawable/gesture_pattern_normal" />
</RelativeLayout>