Hot Article Preview | Click Title to Read
Big news, Google finally takes action, moving closer to iOS? Android 9.0 will prohibit developers from using unofficial APIs
Incredible! Complete source code for 74 apps!
The truth learned after 10 years of work: I wish I had known earlier that positions matter more than now
Source: http://www.apkbus.com/blog-914188-76660.html
Introduction
Recently, project requirements: to add gesture password and fingerprint verification in the project. Coincidentally, I have been practicing custom views lately, so I referred to some online resources and my understanding to achieve the following effect.
International practice: Without a picture, you say a JB (trophy)
This GIF is really garbage, those who are interested can check the demo, I will post the link later.
1. Analyze the effect diagram:
All custom views are created by analyzing the effect diagram, breaking it down into modules, implementing each module, and finally assembling them into a whole. Let’s analyze the gesture password effect diagram below.
From the above image, we can analyze the View as follows:
(1) The total size of the View can be determined by the width of the large gesture View plus the height of the small gesture View. We can assign layout size through the onMeasure method using setMeasuredDimension(width, width + minHeight), where minHeight can be assigned based on actual conditions.
(2) The large gesture View can determine its square size by comparing width and height in the onMeasure method.
(3) The small gesture View is similar to the large gesture View, and the position of the prompt text is also easy to confirm.
(4) Once we have the width and height of the large gesture View, we can easily determine the coordinates and sizes of each gesture point.
(5) Everyone must have done operations to print various shapes using tags when learning Java. The gesture View is equivalent to a simple 3×3 matrix. Knowing the size and coordinates makes it easy to draw.
(6) The small View can also be implemented similarly. It should be noted that during the first registration of the gesture password, there is a small View, while there is none during authentication. We can set it to be hidden in onDraw based on the state.
2. After analyzing, let’s implement it step by step:
1. The template should be generic and customizable, so we need to define some attrs.
Based on the effect diagram analysis, I defined the attrs as follows. Here, I used images for the gesture points (images can make the gesture points cooler).
<declare-styleable name="SecurityCenter">
<!-- Selected state of gesture point-->
<attr name="selectedBitmap" format="reference"/>
<!-- Unselected state of gesture point-->
<attr name="unselectedBitmap" format="reference"/>
<!-- Selected state of small gesture point-->
<attr name="selectedBitmapSmall" format="reference"/>
<!-- Unselected state of small gesture point-->
<attr name="unselectedBitmapSmall" format="reference"/>
<!-- Interception time for re-verification after failure-->
<attr name="waitTime" format="integer"/>
<!-- Maximum number of verification failures-->
<attr name="maxFailCounts" format="integer"/>
<!-- Minimum number of points to connect when drawing-->
<attr name="minPoint" format="integer"/>
<!-- Font color-->
<attr name="paintColor" format="color"/>
<!-- Font size-->
<attr name="paintTextSize" format="dimension"/>
</declare-styleable>
2. In the View, receive the assigned values. I used bitmap for the point images, and if no specific definition is provided, these attributes will all have default values. The code is as follows:
public ChaosGestureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.SecurityCenter);
Drawable dw_selected = ta.getDrawable(R.styleable.SecurityCenter_selectedBitmap);
Drawable dw_unSeclect = ta.getDrawable(R.styleable.SecurityCenter_unselectedBitmap);
Drawable dw_selected_small = ta.getDrawable(R.styleable.SecurityCenter_selectedBitmapSmall);
Drawable dw_unSeclect_small = ta.getDrawable(R.styleable.SecurityCenter_unselectedBitmapSmall);
if (dw_selected!=null){
selectedBitmap = ((BitmapDrawable) dw_selected).getBitmap();
}else{
selectedBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_finger_selected);
}
if (dw_unSeclect!=null){
unSelectedBitmap = ((BitmapDrawable) dw_unSeclect).getBitmap();
}else{
unSelectedBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_finger_unselected);
}
if (dw_selected_small!=null){
selectedBitmapSmall = ((BitmapDrawable) dw_selected_small).getBitmap();
}else{
selectedBitmapSmall = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_finger_selected_small);
}
if (dw_unSeclect_small!=null){
unSelectedBitmapSmall= ((BitmapDrawable) dw_unSeclect_small).getBitmap();
}else{
unSelectedBitmapSmall = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_finger_unselected_new);
}
//Waiting time, default 30s
waitTime = ta.getInteger(R.styleable.SecurityCenter_waitTime,30);
//Attempt count, default 5
tempCount = ta.getInteger(R.styleable.SecurityCenter_maxFailCounts,5);
//Minimum number of points set, default 4
minPointNums = ta.getInteger(R.styleable.SecurityCenter_minPoint,4);
//Set the color of the paint
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStrokeWidth(10);
mPaint.setStyle(Paint.Style.STROKE);
//Color of the paint
int color = ta.getColor(R.styleable.SecurityCenter_paintColor, context.getResources().getColor(R.color.black));
mPaint.setColor(color);
//Font size
float textsize = ta.getDimension(R.styleable.SecurityCenter_paintTextSize, 40);
mPaint.setTextSize(textsize);
//Avoid errors when recreated
ta.recycle();
initView(context);
}
3. In onMeasure, measure the layout size of the gesture View, which is easy to understand based on the initial analysis.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//width is the unit width of the large View
int width = Math.min(widthSize, heightSize);
if (widthMode == MeasureSpec.UNSPECIFIED) {
width = heightSize;
} else if (heightMode == MeasureSpec.UNSPECIFIED) {
width = widthSize;
}
//The height of the large gesture View is a square with side length of width, panelHeight is reserved for the small gesture View
setMeasuredDimension(width, width + panelHeight);
}
4. Through the onSizeChange method, we can obtain the size of the large gesture password points and small gesture password points based on the value of mLineHeight (3*1 row height), and then set the size of the gesture points through Bitmap.createScaledBitmap method. Details can be seen in the code comments below:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mPanelWidth = Math.min(w, h);
//The width of the large gesture point is 0.6 times the unit width, making it look better and not too full
pieceWidth = (int) (mLineHeight * 0.6f);
//The width of the small gesture point is similar
pieceWidthSmall = (int) (mLineHeight * 0.15f);
//Draw the corresponding gesture point size
selectedBitmap = Bitmap.createScaledBitmap(selectedBitmap, (int) pieceWidth, (int) pieceWidth, false);
unSelectedBitmap = Bitmap.createScaledBitmap(unSelectedBitmap, (int) pieceWidth, (int) pieceWidth, false);
selectedBitmapSmall = Bitmap.createScaledBitmap(selectedBitmapSmall, (int) pieceWidthSmall, (int) pieceWidthSmall, false);
unSelectedBitmapSmall = Bitmap.createScaledBitmap(unSelectedBitmapSmall, (int) pieceWidthSmall, (int) pieceWidthSmall, false);
}
5. We know that GestureView generally has two states: one is the registration state (which includes the small View), and the other is the authentication state (which does not include the small View). Therefore, we need to define two states to distinguish usage conditions.
//Gesture initialization registration state
public static final int STATE_REGISTER = 101;
//Gesture confirmation usage state
public static final int STATE_LOGIN = 100;
//Set a parameter to record whether it is in the initialization stage or the usage stage, default is confirmation state
private int stateFlag = STATE_LOGIN;
When we successfully register, we need to save the gesture state, which we store in SharedPreferences.
//Save state after success
private boolean saveState() {
SharedPreferences sp = mContext.getSharedPreferences("STATE_DATA", Activity.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putInt("state", stateFlag);
return edit.commit();
}
Similarly, before initialization, we need to obtain the state to determine what state the current View belongs to, so that we can determine whether to draw the small View in onDraw.
//Get the current View state from SP, default is initialization state
private int getState() {
SharedPreferences mSharedPreference = mContext.getSharedPreferences("STATE_DATA", Activity.MODE_PRIVATE);
return mSharedPreference.getInt("state", STATE_REGISTER);
}
6. Based on the state, draw the gesture password points, connection lines, and prompt text.
(1) Draw 9 unselected large gesture View points using the canvas.drawBitmap(Bitmap bitmap, float left, float top, Paint paint) method. Here we need to pay attention to the position calculation, as in the Android screen coordinate system, the upper left position is (0,0), and positive is to the right and down.
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
canvas.drawBitmap(unSelectedBitmap, (float) (mLineHeight * (j + 0.5) - pieceWidth / 2), (float) (mLineHeight * (i + 0.5) - pieceWidth / 2 + panelHeight), mPaint);
}
}
It may sound abstract, but it is actually simple once you understand the coordinates. Draw a picture to assist your understanding; the small View is reserved for height (panelHeight):
(2) You may be curious about how the gesture connection line is stored and verified! Good question! I also thought about this for a long time. Some wheels use a two-dimensional array to achieve this, and through this two-dimensional array, I thought of using a Bean. I used a Bean to store the X and Y coordinates of the corresponding points and added each point instance to a List<>, thus completing the storage of all gesture drawing points.
The Bean code is as follows:
//Define Bean to store gesture coordinates
public class GestureBean {
private int x;
private int y;
@Override
public String toString() {
return "GestureBean{" +
"x=" + x +
", y=" + y +
'}';
}
public GestureBean(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
@Override
public boolean equals(Object o) {
return ((GestureBean) o).getX() == x && ((GestureBean) o).getY() == y;
}
}
(3) Draw connection lines and selected points: The connection lines are drawn through onTouchEvent and onDraw. In onTouchEvent, the points traversed by the finger will all be stored in the listDatas collection, and then through the invalidate(); method notify onDraw, based on the number of new points in listDatas, to draw the selected points and the connection lines between the points. Here is only the code in onDraw; the logic in onTouchEvent will be detailed later.
//Used to judge the state
GestureBean firstGestrue = null;
GestureBean currGestrue = null;
if (!listDatas.isEmpty()) {
firstGestrue = listDatas.get(0);
//Draw connection line
for (int i = 1; i < listDatas.size(); i++) {
currGestrue = listDatas.get(i);
canvas.drawLine((float) (mLineHeight * (firstGestrue.getX() + 0.5)), (float) (mLineHeight * (firstGestrue.getY() + 0.5) + panelHeight), (float) (mLineHeight * (currGestrue.getX() + 0.5)), (float) (mLineHeight * (currGestrue.getY() + 0.5) + panelHeight), mPaint);
firstGestrue = currGestrue;
}
//Last line
lastGestrue = listDatas.get(listDatas.size() - 1);
canvas.drawLine((float) (mLineHeight * (lastGestrue.getX() + 0.5)), (float) (mLineHeight * (lastGestrue.getY() + 0.5) + panelHeight), currX, currY, mPaint);
//Traverse the array and change the selected points' images
for (GestureBean bean : listDatas) {
canvas.drawBitmap(selectedBitmap, (float) (mLineHeight * (bean.getX() + 0.5) - pieceWidth / 2), (float) (mLineHeight * (bean.getY() + 0.5) + panelHeight - pieceWidth / 2), mPaint);
}
}
When the gesture is successfully registered, we need to save the gesture collection for the next verification, which we store in SharedPreference (note: this gesture View is only applicable for local interception, so storing in SharedPreferences is sufficient).
//Store the x and y list into sp
private boolean saveToSharedPrefference(List<GestureBean> data) {
SharedPreferences sp = mContext.getSharedPreferences("GESTURAE_DATA", Activity.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
//Store how many points
edit.putInt("data_size", data.size()); /*sKey is an array*/
//Coordinates of each point
for (int i = 0; i < data.size(); i++) {
edit.remove("data_" + i);
edit.putString("data_" + i, data.get(i).getX() + " " + data.get(i).getY());
} return edit.commit();
}
To retrieve the stored collection: When we store and retrieve, we can first store the number of input points for easier judgment.
//Read the previously saved List
public List<GestureBean> loadSharedPrefferenceData() {
List<GestureBean> list = new ArrayList<>();
SharedPreferences mSharedPreference = mContext.getSharedPreferences("GESTURAE_DATA", Activity.MODE_PRIVATE);
//Get the number of points
int size = mSharedPreference.getInt("data_size", 0);
//And the coordinates
for (int i = 0; i < size; i++) {
String str = mSharedPreference.getString("data_" + i, "0 0");
list.add(new GestureBean(Integer.parseInt(str.split(" ")[0]), Integer.parseInt(str.split(" ")[1])));
} return list;
}
(4) Drawing the small image and text is straightforward. I will directly provide the code (I was interrupted during a meeting, and my thoughts were disrupted…).
//If in initialization state
if (stateFlag == STATE_REGISTER) {
//Draw the prompt point above No prompt point needed
drawTipsPoint(canvas);
} else {
//Above is text, points are gone
drawTipsText(canvas);
}
It should be noted that the small View needs to retain the style of the first drawing when completing the first drawing. This is done by storing the comparison through a list, as shown in the following code.
//Draw prompt points
private void drawTipsPoint(Canvas canvas) {
//Width is half of the View width
float widthMiddleX = mPanelWidth / 2;
//Determine the relevant coordinates and find the center point of the first point
float firstX = widthMiddleX - pieceWidthSmall / 4 - pieceWidthSmall / 2 - pieceWidthSmall;
float firstY = panelHeight / 2 - pieceWidthSmall / 2 - pieceWidthSmall - pieceWidthSmall / 4 - 10;
//Draw points, as there are no selected points, draw 9 unselected points
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
canvas.drawBitmap(unSelectedBitmapSmall, (float) (firstX + j * (pieceWidthSmall * 1.25)), (float) (firstY + i * (pieceWidthSmall * 1.25)), mPaint);
}
}
//Before the second confirmation, the small gesture password shows the traces of the first drawing
if (listDatasCopy != null && !listDatasCopy.isEmpty()) {
for (GestureBean bean : listDatasCopy) {
canvas.drawBitmap(selectedBitmapSmall, (float) (firstX + bean.getX() * (pieceWidthSmall * 1.25)), (float) (firstY + bean.getY() * (pieceWidthSmall * 1.25)), mPaint);
}
}
//As the finger ActionMove changes the color of the selected point
else if (listDatas != null && !listDatas.isEmpty()) {
for (GestureBean bean : listDatas) {
canvas.drawBitmap(selectedBitmapSmall, (float) (firstX + bean.getX() * (pieceWidthSmall * 1.25)), (float) (firstY + bean.getY() * (pieceWidthSmall * 1.25)), mPaint);
}
}
drawMessage(canvas, "Draw unlock pattern", mError);
}
Effect diagram as follows:
Drawing text is straightforward; just determine the general coordinates, and it’s easy to understand. I will directly provide the code:
//Draw prompt text
private void drawTipsText(Canvas canvas) {
float widthMiddleX = mPanelWidth / 2;
mPaint.setStyle(Paint.Style.FILL);
int widthStr1 = (int) mPaint.measureText("Enter gesture to unlock");
float baseX = widthMiddleX - widthStr1 / 2;
float baseY = panelHeight / 2 + 50;
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float fontTotalHeight = fontMetrics.bottom - fontMetrics.top;
float offY = fontTotalHeight / 2 - fontMetrics.bottom - 30;
float newY = baseY + offY;
canvas.drawText("Enter gesture to unlock", baseX, newY, mPaint);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStrokeWidth(10);
}
7. Setting the gesture password definitely requires handling OnTouchEvent, and the logic here is key. I will analyze it in detail.
(1) Here we have encapsulated it quite well. I handled the case where verification exceeds the maximum attempts, which will intercept the gesture View. However, this may seem redundant for organizing ideas, so I will just paste the code for now; feel free to check the demo:
if (mTimeOut) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (0 < leftTime && leftTime <= 30) {
AlertUtil.t(mContext, "Maximum number of attempts reached, retry in " + leftTime + "s");
}
return true;
}
}
(2) First, we need to determine whether our OnTouch event is within the range of the large View. Since the coordinates are clearly specified from the start, this is easy to judge.
if (event.getY() >= ((mLineHeight * (0 + 0.5) - pieceWidth / 2 + panelHeight))){
//Get XY to determine which point the finger is at
int x = (int) ((event.getY() - panelHeight) / mLineHeight);
int y = (int) (event.getX() / mLineHeight);
//Current finger coordinates
currX = event.getX();
currY = event.getY();
}
(3) MotionEvent.ACTION_DOWN: When the finger is pressed down, we need to determine which large gesture point the pressed point falls within and add it to the List<bean>. Notify onDraw to redraw, as described earlier, to show that the first point is selected.
case MotionEvent.ACTION_DOWN:
lastGestrue = null;
if (currX >= 0 && currX <= mPanelWidth && currY >= panelHeight && currY <= panelHeight + mPanelWidth) {
if (currY <= (x + 0.5) * mLineHeight + pieceWidth / 2 + panelHeight && currY >= (x + 0.5) * mLineHeight - pieceWidth / 2 + panelHeight &&
currX <= (y + 0.5) * mLineHeight + pieceWidth / 2 && currX >= (y + 0.5) * mLineHeight - pieceWidth / 2) {
//Determine which point the current finger is in range of. If the point does not exist in listData, store it as the first point
if (!listDatas.contains(new GestureBean(y, x))) {
listDatas.add(new GestureBean(y, x));
}
}
}
//Redraw once, the first point shows as selected
invalidate();
break;
(4) MotionEvent.ACTION_MOVE: When the finger slides on the View, it adds the coordinates of the point it slides to the List<bean>. Then notify to redraw.
case MotionEvent.ACTION_MOVE:
//Finger moves within the range of the large View
if (currX >= 0 && currX <= mPanelWidth && currY >= panelHeight && currY <= panelHeight + mPanelWidth) {
//Reduce the response range. Here it should be noted that x and currX are physically opposite
if (currY <= (x + 0.5) * mLineHeight + pieceWidth / 2 + panelHeight && currY >= (x + 0.5) * mLineHeight - pieceWidth / 2 + panelHeight &&
currX <= (y + 0.5) * mLineHeight + pieceWidth / 2 && currX >= (y + 0.5) * mLineHeight - pieceWidth / 2) {
//If the point is not in listData, store it
if (!listDatas.contains(new GestureBean(y, x))) {
listDatas.add(new GestureBean(y, x));
}
}
}
//Redraw
invalidate();
break;
(5) MotionEvent.ACTION_UP: There are two situations: 1. In authentication state, it will compare with the previously retrieved listdatas from loadSharedPrefferenceData to determine if it is successful. 2. In registration state, it will compare the first listdatas to determine if the two verifications are consistent, thus handling the success and failure logic.
case MotionEvent.ACTION_UP:
if (lastGestrue != null) {
currX = (float) ((lastGestrue.getX() + 0.5) * mLineHeight);
currY = (float) ((lastGestrue.getY() + 0.5) * mLineHeight);
}
//If the View is in authentication state
if (stateFlag == STATE_LOGIN) {
//If they are the same, authentication is successful
if (listDatas.equals(loadSharedPrefferenceData())) {
mError = false;
postListener(true);
invalidate();
listDatas.clear();
return true;
} else {
if (--tempCount == 0) {//Maximum attempts reached
mError = true;
mTimeOut = true;
listDatas.clear();
Date date = new Date();
PreferenceCache.putGestureTime(date.getTime());
mTimerTask = new InnerTimerTask(handler);
mTimer.schedule(mTimerTask, 0, 1000);
invalidate();
return true;
}
mError = true;
AlertUtil.t(mContext, "Gesture error, you can input " + tempCount + " more times");
listDatas.clear();
}
}
//If the View is in registration state
else if (stateFlag == STATE_REGISTER) {
//First authentication state
if (listDatasCopy == null || listDatasCopy.isEmpty()) {
if (listDatas.size() < minPointNums) {
listDatas.clear();
mError = true;
AlertUtil.t(mContext, "The number of points cannot be less than " + minPointNums + " points");
invalidate();
return true;
}
listDatasCopy.addAll(listDatas);
listDatas.clear();
mError = false;
AlertUtil.t(mContext, "Please draw again");
} else {
//If the two authentications are successful
if (listDatas.equals(listDatasCopy)) {
saveToSharedPrefference(listDatas);
mError = false;
stateFlag = STATE_LOGIN;
postListener(true);
saveState();
} else {
mError = true;
AlertUtil.t(mContext, "Inconsistency with the previous gesture drawing, please reset");
}
listDatas.clear();
invalidate();
return true;
}
}
invalidate();
break;
To this end, the logic of the gesture View has been roughly clarified, and the next step is to improve it.
3. Improve the View, set interface calls, failure countdown, and clear current SP cache when closing the View.
1. Observant friends will notice that in ACTION.UP there are postListener(true) and similar calls. This is the interface I defined to return the authentication status. (1) Define the interface, with three parameters: the state of the view, the stored List, and whether it was successful (registration or authentication).
//Define the interface to pass the View state
public interface GestureCallBack{
void gestureVerifySuccessListener(int stateFlag, List<GestureBean> data, boolean success);
}
(2) Instantiate the interface; the current Activity must implement the interface.
//The current Activity must implement the View interface
try {
estureCallBack = (GestureCallBack) mContext;
} catch (final ClassCastException e) {
throw new ClassCastException(mContext.toString() + " must implement GestureCallBack");
}
(3) Pass the interface real-time data in Action.UP.
//Pass data to the interface
private void postListener(boolean success) {
if (gestureCallBack != null) {
gestureCallBack.gestureVerifySuccessListener(stateFlag, listDatas, success);
}
}
2. The failure countdown is implemented through Handler, Timer, and TimerTask (1) Define an inner TimerTask class to record the error countdown.
//Define an inner TimerTask class for recording the error countdown
static class InnerTimerTask extends TimerTask{
Handler handler;
public InnerTimerTask(Handler handler) {
this.handler = handler;
}
@Override
public void run() {
handler.sendMessage(handler.obtainMessage());
}
}
(2) Instantiate; the last error time is also stored in SharedPreference, waitTime is the defined timeout period. The code is as follows:
mTimer = new Timer();
//Calculate the difference between the last failure time and the current time
try {
long lastTime = PreferenceCache.getGestureTime();
Date date = new Date();
if (lastTime !=0 && (date.getTime()-lastTime)/1000<waitTime){
//Failure time not reached, still in locked state
mTimeOut = true;
leftTime = (int)(waitTime-((date.getTime()-lastTime))/1000);
mTimerTask = new InnerTimerTask(handler);
mTimer.schedule(mTimerTask,0,1000);
}else{
mTimeOut = false;
leftTime = waitTime;
}
}catch (RuntimeException e){
e.printStackTrace();
}
(3) Handler processes messages:
//Receive TimerTask messages to notify UI
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
leftTime--;
if (leftTime == 0){
if (mTimer != null)
mTimerTask.cancel();
mTimeOut = false;
AlertUtil.t(mContext,"Please draw the unlock pattern");
mError = false;
invalidate();
//Restore timing information
reSet();
return;
}
mError = true;
invalidate();
}
};
3. Clear the gesture View cache for closing the View or changing the password.
//Clear previously saved state for closing the View
public boolean clearCache() {
SharedPreferences sp = mContext.getSharedPreferences("STATE_DATA", Activity.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putInt("state", STATE_REGISTER);
stateFlag = STATE_REGISTER;
invalidate(); return edit.commit();
}
//To change the gesture password, clear the previous password
public boolean clearCacheLogin() {
SharedPreferences sp = mContext.getSharedPreferences("STATE_DATA", Activity.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putInt("state", STATE_LOGIN);
stateFlag = STATE_LOGIN;
invalidate();
return edit.commit();
}
4. Simple usage:
1. Taking setting the gesture password as an example: (1) XML layout
<LinearLayout 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:orientation="vertical"
tools:context="com.chaos.chaossecuritycenter.activity.SettingPatternPswActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/tv_setting_back"
android:layout_marginLeft="10dp"
android:textSize="16sp"
android:drawableLeft="@mipmap/back"
android:textColor="@color/bak_blue"
android:gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Back"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Set Gesture Password"
android:textColor="@color/black"
android:textSize="20sp" />
</RelativeLayout>
<com.chaos.chaossecuritycenter.weight.ChaosGestureView
android:id="@+id/gesture"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:layout_marginTop="40dp"
android:layout_weight="1"
app:selectedBitmap="@mipmap/icon_finger_selected"
app:unselectedBitmap="@mipmap/icon_finger_unselected"
app:selectedBitmapSmall="@mipmap/icon_finger_selected_small"
app:unselectedBitmapSmall="@mipmap/icon_finger_unselected_new"
app:waitTime="30"
app:maxFailCounts="5"
app:minPoint="4"
app:paintColor="@color/bak_blue"
app:paintTextSize="15sp"
/>
</LinearLayout>
Layout preview:
Java code (setting gesture password page):
public class SettingPatternPswActivity extends AppCompatActivity implements ChaosGestureView.GestureCallBack{
private TextView tv_back;
private ChaosGestureView gestureView;
private int jumpFlg;
private int flag;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setting_pattern_psw);
jumpFlg = getIntent().getIntExtra("jumpFlg", 0);
flag = getIntent().getIntExtra("flag", 0);
initView();
}
private void initView() {
tv_back = (TextView) findViewById(R.id.tv_setting_back);
gestureView = (ChaosGestureView) findViewById(R.id.gesture);
gestureView.setGestureCallBack(this);
//Not calling this method will cause the program to directly enter gesture recognition instead of gesture setting on the second launch
gestureView.clearCache();
tv_back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
@Override
public void gestureVerifySuccessListener(int stateFlag, List<ChaosGestureView.GestureBean> data, boolean success) {
if (stateFlag == GestureView.STATE_LOGIN) {
PreferenceCache.putGestureFlag(true);
finish();
}
}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
}
5. Summary
This custom gesture password is designed based on some wheels and my understanding, mimicking Alipay’s gesture password design. Overall, I have analyzed it for you. This View still has many areas for improvement, and I will gradually improve it. If you have any advice or questions, please leave a comment below.
Demo:
1. Custom View for gesture password 2. Fingerprint verification (due to the security of Alipay’s imitation, I did it together with fingerprint using third-party)
Demo link: https://github.com/ChaosOctopus/ChaosSecurityCenter
If you find it useful, please give me a thumbs up or a star.
If you have good articles to share, feel free to submit them by sending me the article link directly.
Java and Android architecture
Scan the QR code below or click to receive advanced resources for Android, Python, AI, Java, etc.
After following, reply “Baidu”, “Ali”, “Tencent”, or “Resources” for surprises
Public account:JANiubility
Welcome to join our Java and Android architecture circle, with nearly 1000 people joining for learning and communication. More learning resources are updated, and more exchanges lead to progress.
Click below “Read the original text” to get more learning materials.