今天老板提出新项目要使用手势解锁,虽然以前用别人的代码实现过,但随着时间的推移我想自己写一个。
以下是效果图
有点粗糙,但勉强能用,下面附上代码,留作笔记。望各位大神指点。
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.***.***.interfaces.GrapNotif;
/**
* Created by 郭月森 on 2018/6/22.
*/
public class GraphicUnlocking extends View implements View.OnTouchListener{
//密码记录
private StringBuffer pass = new StringBuffer();
//坐标记录
private int [][] coordinate = new int[9][2];
//结尾直线点记录
private float startX=0;
private float startY=0;
private float endX=0;
private float endY=0;
//对外接口
private GrapNotif notif = null;
//默认颜色
private static int DEFAULT_COLOR = Color.parseColor("#cccccc");
//选中颜色
private static int PITCH_ON_COLOR = Color.parseColor("#0088ff");
public GraphicUnlocking(Context context) {
super(context);
setOnTouchListener(this);
}
public GraphicUnlocking(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setOnTouchListener(this);
}
public GraphicUnlocking(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
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);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//初始化所有点的坐标
getCoordinate();
//绘出所有点
for(int[] i: coordinate){
Paint p = new Paint();
//设置画笔宽度
p.setStrokeWidth(4);
//抗锯齿
p.setAntiAlias(true);
p.setColor(DEFAULT_COLOR);
//设置画圆圈
p.setStyle(Paint.Style.STROKE);
//绘画
canvas.drawCircle(i[0],i[1],60,p);
}
//创建选中状态的画笔
Paint p0 = new Paint();
p0.setColor(PITCH_ON_COLOR);
p0.setStrokeWidth(10);
p0.setAntiAlias(true);
for (int j = 0;j<pass.length();j++){
//画出选中的圆
canvas.drawCircle(coordinate[pass.charAt(j)-'0'][0],coordinate[pass.charAt(j)-'0'][1],48,p0);
//判断是否需要连接线
if (j>0){
//画出连接线
canvas.drawLine(coordinate[pass.charAt(j-1)-'0'][0],coordinate[pass.charAt(j-1)-'0'][1],
coordinate[pass.charAt(j)-'0'][0],coordinate[pass.charAt(j)-'0'][1],p0);
}
}
if (startX!=0&&endX!=0){
//画出最后一根线
canvas.drawLine(startX,startY,endX,endY,p0);
}
}
/**
* 计算获取所有点的坐标
*/
private void getCoordinate(){
int w = getWidth();
int h = getHeight();
//得到点之间的距离
int c = w/4;
//得到点与高之间的差(这里只实现了竖屏,高比宽大,所以是以宽为基准,使9个点在布局的中心)
int x = (h-w)/2;
//得到每个点的XY坐标
for (int i = 0;i<9;i++){
coordinate[i][0] = c+((i%3)*c);
coordinate[i][1] = c+x+((i/3)*c);
}
}
//监听滑动操作
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()){
//手指按下
case MotionEvent.ACTION_DOWN:
//清空数据
cliar();
//获取按下位置有效性或按下的点
int p = getPoint(motionEvent.getX(),motionEvent.getY());
//判断有效性
if (p != -1) {
//保存直线的起点
startX = coordinate[p][0];
startY = coordinate[p][1];
//保存点
pass.append(p);
//刷新界面
invalidate();
if (notif!=null){
//通知activity
notif.Start(p);
}
}else {
invalidate();
return false;
}
break;
case MotionEvent.ACTION_MOVE://手指滑动
//获取手指当前位置或当前经过的点
int pm = getPoint(motionEvent.getX(),motionEvent.getY());
//判断是否进过了点
if (pm != -1){
//更新直线起点坐标
startX =coordinate[pm][0];
startY = coordinate[pm][1];
//保存点
pass.append(pm);
if (notif!=null){
//通知activity
notif.Move(pm,pass.toString());
}
}else {
//更新直线结束点坐标
endX = motionEvent.getX();
endY = motionEvent.getY();
}
invalidate();
break;
case MotionEvent.ACTION_UP:
//重置直线坐标但不取消已绘制的图形
startX = 0;
startY = 0;
endX = 0;
endY = 0;
if (notif!=null){
//通知activity最终密码
notif.Stop(pass.toString());
}
invalidate();
break;
}
return true;
}
/**
* 将密码归零及重置直线坐标
*/
public void cliar(){
pass.setLength(0);
startX = 0;
startY = 0;
endX = 0;
endY = 0;
invalidate();
}
/**
* 判断坐标有效性
* @param x
* @param y
* @return为-1时无效,其它为点
*/
private int getPoint(float x,float y){
int back = -1;
//遍历所有坐标
for (int i = 0;i<coordinate.length;i++){
//判断坐标是否有效,30为有效范围,可更改
if (Math.abs(x-coordinate[i][0])<30&&Math.abs(y-coordinate[i][1])<30){
back = i;
break;
}
}
//遍历密码
for (int k = 0;k<pass.length();k++){
int j = pass.charAt(k)-'0';
//如果密码已经存在则无效
back = back==j?-1:back;
}
return back;
}
/**
* 设置通知接口
* @param notif
*/
public void setNotif(GrapNotif notif) {
this.notif = notif;
}
}
以上是布局的实现设置密码和校验密码的逻辑需要单独在activity里面做。
接下来是使用方法
<com.*****.view.GraphicUnlocking
android:id="@+id/test_grap"
android:layout_width="match_parent"
android:layout_height="match_parent" />
直接在xml中使用就可以了,加上id就可以在activity里操作该布局(例如传入通知接口)
下面是个人定义的接口
/**
* Created by 郭月森 on 2018/6/25.
*/
public interface GrapNotif {
/**
* 开始
* @param p 第一个点
*/
public void Start(int p);
/**
* 移动时经过有效点
* @param p 经过的点
* @param pass 已经输入的密码
*/
public void Move(int p, String pass);
/**
* 滑动结束
* @param pass 滑动密码
*/
public void Stop(String pass);
}
这个布局就一个类和一个接口,基本满足手势解锁的需要。剩下的按照个人需求添加吧!