您的位置:首页 > 移动开发 > Android开发

android应用九宫格图案锁

2016-03-10 13:46 323 查看
本文源码来源于http://download.csdn.net/download/zhu104/6888127,特此声明。

android应用上的图案锁是一种设立密码的方法,比如支付宝的密码锁,下面我来分析一下其大致的设计过程。

首先,需要自定义一个View,在View中展现密码锁,并设立监听器。

其onDraw函数如下

@Override
public void onDraw(Canvas canvas)
{
if (!isCache)
{
initCache();
}
drawToCanvas(canvas);
}


initCache()是为了设置图案的位置等信息,其代码如下
private void initCache()
{
//获取View的宽高
w = this.getWidth();
h = this.getHeight();
//x,y为第一个图案的x,y坐标
float x = 0;
float y = 0;
System.out.println("w:"+w);
System.out.println("h:"+h);
//根据宽高大小,使图案在View的中间部位。
if (w > h)
{
x = (w - h) / 2;
w = h;
}
else
{
y = (h - w) / 2;
h = w;
}
//分别为正常图片,点击后的图片,点击后错误的图片
locus_round_original = BitmapFactory.decodeResource(this.getResources(), R.drawable.locus_round_original);
locus_round_click = BitmapFactory.decodeResource(this.getResources(), R.drawable.locus_round_click);
locus_round_click_error = BitmapFactory.decodeResource(this.getResources(), R.drawable.locus_round_click_error);
//图案与图案之间的线
locus_line = BitmapFactory.decodeResource(this.getResources(), R.drawable.locus_line);
//图案与图案之间的线的转角部分
locus_line_semicircle = BitmapFactory.decodeResource(this.getResources(), R.drawable.locus_line_semicircle);
//semicircle半圆形
//错误的线
locus_line_error = BitmapFactory.decodeResource(this.getResources(), R.drawable.locus_line_error);
//错误的线的转角部分
locus_line_semicircle_error = BitmapFactory.decodeResource(this.getResources(), R.drawable.locus_line_semicircle_error);
//方向箭头
locus_arrow = BitmapFactory.decodeResource(this.getResources(), R.drawable.locus_arrow);
// Log.d("Canvas w h :", "w:" + w + " h:" + h);
//canvasMinW的最小宽度
float canvasMinW = w;
/*此部分完全可以删去,因为根据以上代码总有w=h
if (w > h)
{
System.out.println("w>h");
canvasMinW = h;
}
*/
//图案的最小宽度
float roundMinW = canvasMinW / 8.0f * 2;
//图案的最小半径
float roundW = roundMinW / 2.f;
System.out.println("roundMinW:"+roundMinW);
System.out.println("roundW:"+roundW);
//偏差,这里我还未弄清楚为何要添加偏差
float deviation = canvasMinW % (8 * 2) / 2;
System.out.println("canvasMinW % (8 * 2) "+canvasMinW % (8 * 2) );
System.out.println("canvasMinW % (8) "+canvasMinW % (8 ) );
System.out.println("deviation:"+deviation);
x += deviation;
x += deviation;
System.out.println("x:"+x);
System.out.println("locus_round_original.getWidth():"+locus_round_original.getWidth());
if (locus_round_original.getWidth() > roundMinW)
{
//图片缩放比例
float sf = roundMinW * 1.0f / locus_round_original.getWidth();
locus_round_original = BitmapUtil.zoom(locus_round_original, sf);
locus_round_click = BitmapUtil.zoom(locus_round_click, sf);
locus_round_click_error = BitmapUtil.zoom(locus_round_click_error, sf);

locus_line = BitmapUtil.zoom(locus_line, sf);
locus_line_semicircle = BitmapUtil.zoom(locus_line_semicircle, sf);

locus_line_error = BitmapUtil.zoom(locus_line_error, sf);
locus_line_semicircle_error = BitmapUtil.zoom(locus_line_semicircle_error, sf);
locus_arrow = BitmapUtil.zoom(locus_arrow, sf);
roundW = locus_round_original.getWidth() / 2;

}
//每张图案的圆点位置
mPoints[0][0] = new Point(x + 0 + roundW, y + 0 + roundW);
mPoints[0][1] = new Point(x + w / 2, y + 0 + roundW);
mPoints[0][2] = new Point(x + w - roundW, y + 0 + roundW);
mPoints[1][0] = new Point(x + 0 + roundW, y + h / 2);
mPoints[1][1] = new Point(x + w / 2, y + h / 2);
mPoints[1][2] = new Point(x + w - roundW, y + h / 2);
mPoints[2][0] = new Point(x + 0 + roundW, y + h - roundW);
mPoints[2][1] = new Point(x + w / 2, y + h - roundW);
mPoints[2][2] = new Point(x + w - roundW, y + h - roundW);
//为每个点设置索引,即密码值,这里注意,二维数组即是数组的数组
int k = 0;
for (Point[] ps : mPoints)
{
for (Point p : ps)
{
p.index = k;
k++;
}
}
//图片的半径
r = locus_round_original.getHeight() / 2;
isCache = true;
}


接下来便是执行drawToCanvas()
private void drawToCanvas(Canvas canvas)
{
//根据Point点的状态,来绘制与之符合的图案,初始化时point状态为original
for (int i = 0; i < mPoints.length; i++)
{
for (int j = 0; j < mPoints[i].length; j++)
{
Point p = mPoints[i][j];
if (p.state == Point.STATE_CHECK)
{
canvas.drawBitmap(locus_round_click, p.x - r, p.y - r, mPaint);
}
else if (p.state == Point.STATE_CHECK_ERROR)
{
canvas.drawBitmap(locus_round_click_error, p.x - r, p.y - r, mPaint);
}
else
{
canvas.drawBitmap(locus_round_original, p.x - r, p.y - r, mPaint);
}
}
}
//sPoints为已经点中的Point列表,要在这里画出连接的直线,初始化时不用画出直线
if (sPoints.size() > 0)
{
int tmpAlpha = mPaint.getAlpha();
mPaint.setAlpha(lineAlpha);
Point tp = sPoints.get(0);
for (int i = 1; i < sPoints.size(); i++)
{
Point p = sPoints.get(i);
drawLine(canvas, tp, p);
tp = p;
}
if (this.movingNoPoint)
{
drawLine(canvas, tp, new Point((int) moveingX, (int) moveingY));
}
mPaint.setAlpha(tmpAlpha);
lineAlpha = mPaint.getAlpha();
}
}


如此,九宫格的初始化已经完成,接下来是要设置触摸监听器、处理事件。

@Override
public boolean onTouchEvent(MotionEvent event)
{
//是否允许触摸标志
if (!isTouch) { return false; }
movingNoPoint = false;
float ex = event.getX();
float ey = event.getY();
boolean isFinish = false;
boolean redraw = false;
Point p = null;
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
if (task != null)
{
task.cancel();
task = null;
Log.d("task", "touch cancel()");
}
//将之前点击保存在sPoints的全部清空
reset();
//检查是否点击到图案
p = checkSelectPoint(ex, ey);
if (p != null)
{
//点击到图案,允许记录点击的图案信息,准备核对面膜
checking = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (checking)
{
p = checkSelectPoint(ex, ey);
if (p == null)
{
//表示所触摸位置不是图案,手指在移动,准备画线
movingNoPoint = true;
moveingX = ex;
moveingY = ey;
}
}
break;
case MotionEvent.ACTION_UP:
p = checkSelectPoint(ex, ey);
checking = false;
isFinish = true;
break;
}
if (!isFinish && checking && p != null)
{
//从MotionEvent.ACTION_DOWN:中退出后执行此处
int rk = crossPoint(p);
if (rk == 2) //画线
{
movingNoPoint = true;
moveingX = ex;
moveingY = ey;
redraw = true;
}
else if (rk == 0) //所点击的未加入sPoints即首次被点击
{
p.state = Point.STATE_CHECK;
addPoint(p);
redraw = true;
}
}
if (isFinish)
{
//MotionEvent.ACTION_UP退出后,执行此处
if (this.sPoints.size() == 1)
{
this.reset();
}
else if (this.sPoints.size() < passwordMinLength && this.sPoints.size() > 0)
{
error();//密码长度不正确,设置sPoints的状态为errpr
clearPassword();//先绘制错误显示图,经过800ms后恢复为正常状态
Toast.makeText(this.getContext(), "密码长度不够!", Toast.LENGTH_SHORT).show();
}
else if (mCompleteListener != null)
{
if (this.sPoints.size() >= passwordMinLength)
{
this.disableTouch();
//监听开始处理得到的密码,如判断是否正确等。
mCompleteListener.onComplete(toPointString());
}
}
}
this.postInvalidate();
return true;
}


reset代码如下
private void reset()
{
for (Point p : sPoints)
{
p.state = Point.STATE_NORMAL;
}
sPoints.clear();
this.enableTouch();
}


checkSelectPoint()代码如下
private Point checkSelectPoint(float x, float y)
{
for (int i = 0; i < mPoints.length; i++)
{
for (int j = 0; j < mPoints[i].length; j++)
{
Point p = mPoints[i][j];
if (RoundUtil.checkInRound(p.x, p.y, r, (int) x, (int) y)) { return p; }
}
}
return null;
}
public static boolean checkInRound(float sx, float sy, float r, float x, float y)
{
return Math.sqrt((sx - x) * (sx - x) + (sy - y) * (sy - y)) < r;
}
checkInRound主要是根据两点的距离来判断。

clearPassword()代码如下
public void clearPassword()
{
clearPassword(CLEAR_TIME);
}

public void clearPassword(final long time)
{
if (time > 1)
{
if (task != null)
{
task.cancel();
Log.d("task", "clearPassword cancel()");
}
lineAlpha = 130;
postInvalidate();
task = new TimerTask()
{
public void run()
{
reset();
postInvalidate();
}
};
Log.d("task", "clearPassword schedule(" + time + ")");
timer.schedule(task, time);
}
else
{
reset();
postInvalidate();
}
}


当sPoints.size() > 0即是已经开始记录点信息,准备核对密码时要画出直线,drawLine()的代码如下

private void drawLine(Canvas canvas, Point a, Point b)
{
//计算两点的距离
float ah = (float) MathUtil.distance(a.x, a.y, b.x, b.y);
//获得两个的偏移角度
float degrees = getDegrees(a, b);
//canvas旋转
canvas.rotate(degrees, a.x, a.y);
if (a.state == Point.STATE_CHECK_ERROR)
{
mMatrix.setScale((ah - locus_line_semicircle_error.getWidth()) / locus_line_error.getWidth(), 1);
mMatrix.postTranslate(a.x, a.y - locus_line_error.getHeight() / 2.0f);
canvas.drawBitmap(locus_line_error, mMatrix, mPaint);
canvas.drawBitmap(locus_line_semicircle_error, a.x + locus_line_error.getWidth(), a.y - locus_line_error.getHeight() / 2.0f,
mPaint);
}
else
{
mMatrix.setScale((ah - locus_line_semicircle.getWidth()) / locus_line.getWidth(), 1);
mMatrix.postTranslate(a.x, a.y - locus_line.getHeight() / 2.0f);
canvas.drawBitmap(locus_line, mMatrix, mPaint);
canvas.drawBitmap(locus_line_semicircle, a.x + ah - locus_line_semicircle.getWidth(), a.y - locus_line.getHeight() / 2.0f,
mPaint);
}
canvas.drawBitmap(locus_arrow, a.x, a.y - locus_arrow.getHeight() / 2.0f, mPaint);
canvas.rotate(-degrees, a.x, a.y);
}


现在到向外提供接口的阶段了。当Activity使用这个View时,需要重写这个接口,以实现该Activity需要实现的任务。代码如下

private OnCompleteListener mCompleteListener;

public void setOnCompleteListener(OnCompleteListener mCompleteListener)
{
this.mCompleteListener = mCompleteListener;
}
public interface OnCompleteListener
{
public void onComplete(String password);
}


在九宫格中最重要的自定义View已经差不多讲解完了,大家可以往http://download.csdn.net/download/zhu104/6888127下载完整代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: