看过极客学院自定义图案解锁控件教程,自己尝试写了一个。
效果图:
1.首先要绘制九个点,先看两张图片
通过上面的两张图片可以很容易计算出没个圆的圆心,也就是正方型的顶点坐标。并保存这九个点,要保存这九个点必须先创建一个点类。
package com.example.chl.myapplication;
/**
* Created by chl on 16-4-6.
*/
public class TuAnPoint {
public static int STATE_NORMAL = 1;
public static int STATE_PRESS = 2;
public static int STATE_ERROR = 3;
private float x;
private float y;
private int state = STATE_NORMAL;
public TuAnPoint(float x, float y) {
this.x = x;
this.y = y;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
/**
* 重写equals方法来判断两个点对象是否相等
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
boolean bx = x == ((TuAnPoint) o).getX() ? true : false;
boolean by = y == ((TuAnPoint) o).getY() ? true : false;
boolean bb=false;
if (bx && by){
bb=true;
}
return bb;
}
}
把这些坐标转化成我们定义的点对象并保存在List集合中:
float width = getWidth();
float height = getHeight();
float offsetX = 0;
float offsetY = 0;
float offset = 0;
float length = 0;
offset = Math.abs(width - height) / 2f;
if (width > height) {
//横屏
offsetX = offset;
offsetY = 0;
length = height / 4f;
} else {
//竖屏
offsetX = 0;
offsetY = offset;
length = width / 4f;
}
listPoint.add(new TuAnPoint(offsetX + length, offsetY + length));
listPoint.add(new TuAnPoint(offsetX + 2 * length, offsetY + length));
listPoint.add(new TuAnPoint(offsetX + 3 * length, offsetY + length));
listPoint.add(new TuAnPoint(offsetX + length, offsetY + 2 * length));
listPoint.add(new TuAnPoint(offsetX + 2 * length, offsetY + 2 * length));
listPoint.add(new TuAnPoint(offsetX + 3 * length, offsetY + 2 * length));
listPoint.add(new TuAnPoint(offsetX + length, offsetY + 3 * length));
listPoint.add(new TuAnPoint(offsetX + 2 * length, offsetY + 3 * length));
listPoint.add(new TuAnPoint(offsetX + 3 * length, offsetY + 3 * length));
isInit = true;
2.把保存的点绘制到屏幕上
1)把点在不同状态下的资源图片加载为Bimap
private Bitmap normal = BitmapFactory.decodeResource(getResources(), R.drawable.normal);
private Bitmap error = BitmapFactory.decodeResource(getResources(), R.drawable.error);
private Bitmap press = BitmapFactory.decodeResource(getResources(), R.drawable.press);
2)绘制图片
/**
* 判断点的状态并绘制到屏幕上
* @param canvas
*/
private void drawPoint(Canvas canvas) {
for (TuAnPoint tuAnPoint : listPoint) {
if (tuAnPoint.getState() == TuAnPoint.STATE_PRESS) {
canvas.drawBitmap(normal, tuAnPoint.getX() - radius, tuAnPoint.getY() - radius, null);
} else if (tuAnPoint.getState() ==TuAnPoint.STATE_PRESS) {
canvas.drawBitmap(press, tuAnPoint.getX() - radius, tuAnPoint.getY() - radius, null);
} else {
canvas.drawBitmap(error, tuAnPoint.getX() - radius, tuAnPoint.getY() - radius, null);
}
}
}
因为drawBitmap方法参数是图片左上角的坐标,所以要减去一个图片的半径。
3.onTouchEvent处理
@Override
public boolean onTouchEvent(MotionEvent event) {
mouseX = event.getX();
mouseY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (pressList.size() > 0) {
for (TuAnPoint p : pressList) {
p.setState(TuAnPoint.STATE_NORMAL);
}
pressList.clear();
}
getSelectPoint();
break;
case MotionEvent.ACTION_MOVE:
getSelectPoint();
break;
case MotionEvent.ACTION_UP:
int pressCount = 0;
for (TuAnPoint tuAnPoint : listPoint) {
if (!pressList.contains(tuAnPoint)) {
if (getPointsDistance(tuAnPoint.getX(), tuAnPoint.getY()) <= radius) {
pressCount++;
}
}
}
if (pressCount == 0 && pressList.size() > 0) {
mouseX = pressList.get(pressList.size() - 1).getX();
mouseY = pressList.get(pressList.size() - 1).getY();
}
boolean isFinish = false;
if (finishedListener != null) {
isFinish = finishedListener.onDrawFinished(pressList);
if (!isFinish) {
for (TuAnPoint point : pressList) {
point.setState(TuAnPoint.STATE_ERROR);
}
}
}
break;
}
invalidate();
return true;
}
/**
* 判断当前手指在屏幕上的点是否在九个点的范围之类,如果是则选中改点
*/
private void getSelectPoint() {
for (TuAnPoint tuAnPoint : listPoint) {
if (getPointsDistance(tuAnPoint.getX(), tuAnPoint.getY()) <= radius) {
pressPoint = tuAnPoint;
if (pressList.size() > 0) {
TuAnPoint p = pressList.get(pressList.size() - 1);
for (TuAnPoint isPress : listPoint) {
boolean b = isLine(p, isPress, pressPoint);
//Log.d("tuanjiesuo", "bb:" + b);
if (b) {
if (((isPress.getX() > p.getX() && isPress.getX() < pressPoint.getX()) && ((isPress.getY() > p.getY()) && (isPress.getY() < pressPoint.getY())))
|| ((isPress.getX() < p.getX() && isPress.getX() > pressPoint.getX()) && ((isPress.getY() < p.getY()) && (isPress.getY() > pressPoint.getY())))) {
//Log.d("tuanjiesuo","jinglai");
isPress.setState(TuAnPoint.STATE_PRESS);
if (!pressList.contains(isPress)) {
pressList.add(isPress);
}
}
if ((isPress.getX() == p.getX() && isPress.getX() == pressPoint.getX() && isPress.getY() > p.getY() && isPress.getY() < pressPoint.getY())
|| (isPress.getX() == p.getX() && isPress.getX() == pressPoint.getX() && isPress.getY() < p.getY() && isPress.getY() > pressPoint.getY())) {
isPress.setState(TuAnPoint.STATE_PRESS);
if (!pressList.contains(isPress)) {
pressList.add(isPress);
}
}
if ((isPress.getY() == p.getY() && isPress.getY() == pressPoint.getY() && isPress.getX() > p.getX() && isPress.getX() < pressPoint.getX())
|| (isPress.getY() == p.getY() && isPress.getY() == pressPoint.getY() && isPress.getX() < p.getX() && isPress.getX() > pressPoint.getX())) {
isPress.setState(TuAnPoint.STATE_PRESS);
if (!pressList.contains(isPress)) {
pressList.add(isPress);
}
}
if ((isPress.getX() > p.getX() && isPress.getX() < pressPoint.getX() && isPress.getY() < p.getY() && isPress.getY() > pressPoint.getY())
|| (isPress.getX() < p.getX() && isPress.getX() > pressPoint.getX() && isPress.getY() > p.getY() && isPress.getY() < pressPoint.getY())) {
isPress.setState(TuAnPoint.STATE_PRESS);
if (!pressList.contains(isPress)) {
pressList.add(isPress);
}
}
// break;
}
}
}
if (!pressList.contains(pressPoint)) {
pressList.add(pressPoint);
}
pressPoint.setState(TuAnPoint.STATE_PRESS);
}
}
}
/**
* 判断isPress这个点是否在点p点pressPoint的直线上
*
* @param p
* @param isPress
* @param pressPoint
* @return
*/
private boolean isLine(TuAnPoint p, TuAnPoint isPress, TuAnPoint pressPoint) {
boolean b = (p.getY() - isPress.getY()) * (pressPoint.getX() - p.getX()) == (p.getX() - isPress.getX()) * (pressPoint.getY() - p.getY()) ? true : false;
return b;
}
/**
* 计算传入的坐标到手指在屏幕上点的距离
* @param x
* @param y
* @return
*/
private float getPointsDistance(float x, float y) {
float distance = (float) Math.sqrt((x - mouseX) * (x - mouseX) + (y - mouseY) * (y - mouseY));
return distance;
}
/**
* 提供给外部接口,当绘制完成后调用
*/
public interface OnDrawFinishedListener {
boolean onDrawFinished(List<TuAnPoint> pressList);
}
/**
* 提供外部传入接口对象的方法
* @param listener
*/
public void setFinishedListener(OnDrawFinishedListener listener) {
finishedListener = listener;
}
最后是完整的项目代码,注释比较完整:
package com.example.chl.myapplication;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* Created by chl on 16-4-6.
*/
public class TuAnView extends View {
private List<TuAnPoint> listPoint = new ArrayList<TuAnPoint>();
private List<TuAnPoint> pressList = new ArrayList<TuAnPoint>();//被选中的点保存的集合
private boolean isInit = false;
private Bitmap normal = BitmapFactory.decodeResource(getResources(), R.drawable.normal);
private Bitmap error = BitmapFactory.decodeResource(getResources(), R.drawable.error);
private Bitmap press = BitmapFactory.decodeResource(getResources(), R.drawable.press);
private float radius = normal.getWidth() / 2f;//图片半径
private float mouseX = 0;
private float mouseY = 0;
private TuAnPoint pressPoint;
private Paint errorPaint;//当密码错误时,画线的画笔
private Paint pressPaint;//当密码正确时,画线的画笔
private OnDrawFinishedListener finishedListener;
public TuAnView(Context context) {
super(context);
// init();
}
public TuAnView(Context context, AttributeSet attrs) {
super(context, attrs);
// init();
}
public TuAnView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// init();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isInit) {
init();
}
drawPoint(canvas);//画点
if (pressList.size() > 0) {//判断当有点被选中时才画线
TuAnPoint a = pressList.get(0);//把集合中第一个点作为线的开始点
for (int i = 1; i < pressList.size(); i++) {
drawLine(canvas, a, pressList.get(i));//画线
a = pressList.get(i);//把当前点作为下次画线的起点
}
drawLine(canvas, a, new TuAnPoint(mouseX, mouseY));//当有起点确定之后,手指在屏幕上滑动,但是没确定终点时画线
}
}
/**
* 画线
* @param canvas
* @param a
* @param b
*/
private void drawLine(Canvas canvas, TuAnPoint a, TuAnPoint b) {
if (a.getState() == TuAnPoint.STATE_PRESS) {
canvas.drawLine(a.getX(), a.getY(), b.getX(), b.getY(), pressPaint);
} else {
canvas.drawLine(a.getX(), a.getY(), b.getX(), b.getY(), errorPaint);
}
}
/**
* 判断点的状态并绘制到屏幕上
* @param canvas
*/
private void drawPoint(Canvas canvas) {
for (TuAnPoint tuAnPoint : listPoint) {
if (tuAnPoint.getState() == TuAnPoint.STATE_PRESS) {
canvas.drawBitmap(normal, tuAnPoint.getX() - radius, tuAnPoint.getY() - radius, null);
} else if (tuAnPoint.getState() ==TuAnPoint.STATE_PRESS) {
canvas.drawBitmap(press, tuAnPoint.getX() - radius, tuAnPoint.getY() - radius, null);
} else {
canvas.drawBitmap(error, tuAnPoint.getX() - radius, tuAnPoint.getY() - radius, null);
}
}
}
/**
* 初始化
*/
private void init() {
errorPaint = new Paint();
pressPaint = new Paint();
errorPaint.setColor(Color.RED);
errorPaint.setStrokeWidth(5);
pressPaint.setColor(Color.YELLOW);
pressPaint.setStrokeWidth(5);
float width = getWidth();
float height = getHeight();
float offsetX = 0;
float offsetY = 0;
float offset = 0;
float length = 0;
offset = Math.abs(width - height) / 2f;
if (width > height) {
//横屏
offsetX = offset;
offsetY = 0;
length = height / 4f;
} else {
//竖屏
offsetX = 0;
offsetY = offset;
length = width / 4f;
}
listPoint.add(new TuAnPoint(offsetX + length, offsetY + length));
listPoint.add(new TuAnPoint(offsetX + 2 * length, offsetY + length));
listPoint.add(new TuAnPoint(offsetX + 3 * length, offsetY + length));
listPoint.add(new TuAnPoint(offsetX + length, offsetY + 2 * length));
listPoint.add(new TuAnPoint(offsetX + 2 * length, offsetY + 2 * length));
listPoint.add(new TuAnPoint(offsetX + 3 * length, offsetY + 2 * length));
listPoint.add(new TuAnPoint(offsetX + length, offsetY + 3 * length));
listPoint.add(new TuAnPoint(offsetX + 2 * length, offsetY + 3 * length));
listPoint.add(new TuAnPoint(offsetX + 3 * length, offsetY + 3 * length));
isInit = true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mouseX = event.getX();
mouseY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (pressList.size() > 0) {
for (TuAnPoint p : pressList) {
p.setState(TuAnPoint.STATE_NORMAL);
}
pressList.clear();
}
getSelectPoint();
break;
case MotionEvent.ACTION_MOVE:
getSelectPoint();
break;
case MotionEvent.ACTION_UP:
int pressCount = 0;
for (TuAnPoint tuAnPoint : listPoint) {
if (!pressList.contains(tuAnPoint)) {
if (getPointsDistance(tuAnPoint.getX(), tuAnPoint.getY()) <= radius) {
pressCount++;
}
}
}
if (pressCount == 0 && pressList.size() > 0) {
mouseX = pressList.get(pressList.size() - 1).getX();
mouseY = pressList.get(pressList.size() - 1).getY();
}
boolean isFinish = false;
if (finishedListener != null) {
isFinish = finishedListener.onDrawFinished(pressList);
if (!isFinish) {
for (TuAnPoint point : pressList) {
point.setState(TuAnPoint.STATE_ERROR);
}
}
}
break;
}
invalidate();
return true;
}
/**
* 判断当前手指在屏幕上的点是否在九个点的范围之类,如果是则选中改点
*/
private void getSelectPoint() {
for (TuAnPoint tuAnPoint : listPoint) {
if (getPointsDistance(tuAnPoint.getX(), tuAnPoint.getY()) <= radius) {
pressPoint = tuAnPoint;
if (pressList.size() > 0) {
TuAnPoint p = pressList.get(pressList.size() - 1);//得到此次绘制线时的起始点
for (TuAnPoint isPress : listPoint) {
/**
* 以为有种情况是,当你选中第一个点后直接绕过第二个点而选中第三个点时,
* 默认第二个点也要选中,所以要做以下判断
*/
boolean b = isLine(p, isPress, pressPoint);//判断是否还有其他点在起点和终点的直线上
//Log.d("tuanjiesuo", "bb:" + b);
if (b) {//如果有点在这条直线上,接下来判断改点是在这两点的中间还是两端,是中间的话就要默认选中
if (((isPress.getX() > p.getX() && isPress.getX() < pressPoint.getX()) && ((isPress.getY() > p.getY()) && (isPress.getY() < pressPoint.getY())))
|| ((isPress.getX() < p.getX() && isPress.getX() > pressPoint.getX()) && ((isPress.getY() < p.getY()) && (isPress.getY() > pressPoint.getY())))) {
//Log.d("tuanjiesuo","jinglai");
isPress.setState(TuAnPoint.STATE_PRESS);
if (!pressList.contains(isPress)) {
pressList.add(isPress);
}
}
if ((isPress.getX() == p.getX() && isPress.getX() == pressPoint.getX() && isPress.getY() > p.getY() && isPress.getY() < pressPoint.getY())
|| (isPress.getX() == p.getX() && isPress.getX() == pressPoint.getX() && isPress.getY() < p.getY() && isPress.getY() > pressPoint.getY())) {
isPress.setState(TuAnPoint.STATE_PRESS);
if (!pressList.contains(isPress)) {
pressList.add(isPress);
}
}
if ((isPress.getY() == p.getY() && isPress.getY() == pressPoint.getY() && isPress.getX() > p.getX() && isPress.getX() < pressPoint.getX())
|| (isPress.getY() == p.getY() && isPress.getY() == pressPoint.getY() && isPress.getX() < p.getX() && isPress.getX() > pressPoint.getX())) {
isPress.setState(TuAnPoint.STATE_PRESS);
if (!pressList.contains(isPress)) {
pressList.add(isPress);
}
}
if ((isPress.getX() > p.getX() && isPress.getX() < pressPoint.getX() && isPress.getY() < p.getY() && isPress.getY() > pressPoint.getY())
|| (isPress.getX() < p.getX() && isPress.getX() > pressPoint.getX() && isPress.getY() > p.getY() && isPress.getY() < pressPoint.getY())) {
isPress.setState(TuAnPoint.STATE_PRESS);
if (!pressList.contains(isPress)) {
pressList.add(isPress);
}
}
// break;
}
}
}
if (!pressList.contains(pressPoint)) {
pressList.add(pressPoint);
}
pressPoint.setState(TuAnPoint.STATE_PRESS);
}
}
}
/**
* 判断isPress这个点是否在点p点pressPoint的直线上
*
* @param p
* @param isPress
* @param pressPoint
* @return
*/
private boolean isLine(TuAnPoint p, TuAnPoint isPress, TuAnPoint pressPoint) {
boolean b = (p.getY() - isPress.getY()) * (pressPoint.getX() - p.getX()) == (p.getX() - isPress.getX()) * (pressPoint.getY() - p.getY()) ? true : false;
return b;
}
/**
* 计算传入的坐标到手指在屏幕上点的距离
* @param x
* @param y
* @return
*/
private float getPointsDistance(float x, float y) {
float distance = (float) Math.sqrt((x - mouseX) * (x - mouseX) + (y - mouseY) * (y - mouseY));
return distance;
}
/**
* 提供给外部接口,当绘制完成后调用
*/
public interface OnDrawFinishedListener {
boolean onDrawFinished(List<TuAnPoint> pressList);
}
/**
* 提供外部传入接口对象的方法
* @param listener
*/
public void setFinishedListener(OnDrawFinishedListener listener) {
finishedListener = listener;
}
}
设置密码的Activity:
package com.example.chl.myapplication;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Toast;
import java.util.List;
public class SetTuAnActivity extends AppCompatActivity {
private TuAnView tuAnView;
private List<TuAnPoint> pressList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_set_tu_an);
tuAnView=(TuAnView)findViewById(R.id.tuanjiesuo);
tuAnView.setFinishedListener(new TuAnView.OnDrawFinishedListener() {
@Override
public boolean onDrawFinished(List<TuAnPoint> pressList) {
SetTuAnActivity.this.pressList=pressList;
return true;
}
});
}
public void click(View view){
switch (view.getId()){
case R.id.button1:
((MyAppliction)getApplication()).pressList=pressList;
Toast.makeText(this,"保存成功",Toast.LENGTH_SHORT).show();
finish();
break;
case R.id.button2:
((MyAppliction)getApplication()).pressList=null;
break;
}
}
}
测试密码的Activity:
public class TwoActivity extends AppCompatActivity {
private TuAnView tuAnView;
private List<TuAnPoint> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_two);
list=((MyAppliction)getApplication()).pressList;
tuAnView=(TuAnView)findViewById(R.id.two_tuan);
tuAnView.setFinishedListener(new TuAnView.OnDrawFinishedListener() {
@Override
public boolean onDrawFinished(List<TuAnPoint> pressList) {
boolean b=true;
if (pressList.size()!=list.size()){
b=false;
}else {
for (int i = 0; i < pressList.size(); i++) {
if (!pressList.get(i).equals(list.get(i))) {
b = false;
}
}
}
if (b){
Toast.makeText(TwoActivity.this,"密码正确",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(TwoActivity.this,"密码错误",Toast.LENGTH_SHORT).show();
}
return b;
}
});
}
}