先上图:
其实很简单,不用过多解释,一点点注释就够了。
Java代码:
package com.example.graphicunlock;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Bitmap.Config;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class MainActivity extends Activity implements OnTouchListener {
private RelativeLayout relativeLayout;// 用来摆放九个圆形
private ImageView view;// 用来绘制解锁路径
private Path path;// 划过的路径
private Paint paint;
private Canvas canvas;
private Dot[] array = new Dot[9];// 圆形的数组
private Dot lastDot;// 上一个经过的点
private Bitmap bitmap;// 绘制用的bitmap
private boolean drawing = false;// 是否正在画图
private int radius = 0;// 圆形半径
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 锁定竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// 不显示标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 全屏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
relativeLayout = (RelativeLayout) findViewById(R.id.rela);
view = (ImageView) findViewById(R.id.view);
view.setOnTouchListener(this);
drawDots();
}
/**
* 放置九个圆形 将九个圆形在屏幕中居中放置,每屏幕的三分之一宽度为一格,横竖排各三个,每个圆宽度是屏幕宽度的1/6
*/
protected void drawDots() {
int TopMars = (getScreenHeight() - getScreenWidth()) / 2;
radius = getScreenWidth() / 12;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
radius * 2, radius * 2);
params.leftMargin = (int) (radius * 4 * (j + 0.25));
params.topMargin = (int) (TopMars + radius * 4 * (i + 0.25));
// 新建半径为radius的圆形
Dot dot = new Dot(this, radius);
array[i * 3 + j] = dot;
relativeLayout.addView(dot, params);
}
}
}
/**
* 检查pointF是否在某个圆形范围内
*
* @param point
* 要检查的点
* @return 如果确实在某个圆形范围内,则返回该圆形,反之返回null
*/
private Dot hitValidDot(PointF point) {
for (int i = 0; i < array.length; i++) {
Dot dot = array[i];
if (!dot.getPassed()) {
int[] location = { 0, 0 };
dot.getLocationOnScreen(location);
if (Math.sqrt((point.x - location[0] - radius)
* (point.x - location[0] - radius)
+ (point.y - location[1] - radius)
* (point.y - location[1] - radius)) < radius) {
return dot;
}
}
}
return null;
}
/**
* 要绘制到的目标图片上的触摸事件 本方法里view.invalidate()并不是必须的,有没有一样……
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 检查手机按下的点是否在某个圆形内,如果是则以此圆形为起点开始绘制图形
PointF point = new PointF(event.getRawX(), event.getRawY());
Dot dot = hitValidDot(point);
if (dot != null) {
// 开始绘制 先实例化要绘制的bitmap canvas paint 和绘制的路径path
bitmap = Bitmap.createBitmap(getWindowWidth(),
getWindowHeight(), Config.ARGB_8888);
canvas = new Canvas(bitmap);
paint = new Paint();
path = new Path();
// 获取此圆形中心点的位置
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot
.getLayoutParams();
PointF startPoint = new PointF(params.leftMargin + radius,
params.topMargin + radius);
// 将loasDot赋值给dot,并将dot设置为经过状态
lastDot = dot;
lastDot.drawPassed();
// 将圆形的中心点设置为路径的起点 并设置要绘制路径的颜色的宽度
path.moveTo(startPoint.x, startPoint.y);
paint.setARGB(255, 0, 0, 255);
paint.setStrokeWidth(8);
paint.setStyle(Style.STROKE);
// 绘制到屏幕
view.setImageBitmap(bitmap);
// 标记为正在绘图中
drawing = true;
}
break;
case MotionEvent.ACTION_MOVE:
if (drawing) {
// 先清空图片 否则看到的是每次绘制的叠加效果
clear();
// 同MotionEvent.ACTION_DOWN中一样 检查是否经过了某一点
PointF point2 = new PointF(event.getRawX(), event.getRawY());
Dot dot2 = hitValidDot(point2);
if (dot2 != null) {
// 不过有时候两点之间可能会有第三个点,如果第三个点为非经过状态,则将此点设置为经过状态
Dot dotBetween = checkDotBetween(lastDot, dot2);
if (dotBetween != null) {
lastDot = dotBetween;
lastDot.drawPassed();
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot2
.getLayoutParams();
path.lineTo(params.leftMargin + radius,
params.topMargin + radius);
}
lastDot = dot2;
lastDot.drawPassed();
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot2
.getLayoutParams();
path.lineTo(params.leftMargin + radius, params.topMargin
+ radius);
}
// 绘制出经过的所有点的路径
canvas.drawPath(path, paint);
// 绘制出上一个点到手指触摸的位置的路径
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) lastDot
.getLayoutParams();
canvas.drawLine(params.leftMargin + radius, params.topMargin
+ radius, event.getX(), event.getY(), paint);
view.invalidate();
}
break;
case MotionEvent.ACTION_UP:
if (drawing) {
// 手指抬起后,清空并重新绘制所有经过的点的路径,这样就会清除上一个点到手指触摸的位置的路径了
clear();
canvas.drawPath(path, paint);
view.invalidate();
// 绘制完毕,将绘制状态改为false
drawing = false;
// 三秒种后重置,放在这仅仅是为了测试重置功能
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
clearAllDrawing();
}
}, 3000);
}
break;
default:
break;
}
return true;
}
/**
* 重置所有为初始状态
*/
protected void clearAllDrawing() {
clear();
for (int i = 0; i < array.length; i++) {
Dot dot = array[i];
if (dot != null) {
dot.drawNormal();
}
}
drawing = false;
}
/**
* 查检两点之间是否经过第三点,如果是则返回第三点,否则返回null
*/
protected Dot checkDotBetween(Dot dot1, Dot dot2) {
int[] loc1 = { 0, 0 };
int[] loc2 = { 0, 0 };
dot1.getLocationOnScreen(loc1);
dot2.getLocationOnScreen(loc2);
// 两点之间的中点
PointF pointF = new PointF((loc1[0] + loc2[0]) / 2 + radius,
(loc1[1] + loc2[1]) / 2 + radius);
return hitValidDot(pointF);
}
/**
* 清空画面
*/
protected void clear() {
if (canvas != null && paint != null) {
paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
canvas.drawPaint(paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC));
view.invalidate();
}
}
/**
* @return 屏幕宽度
*/
public int getScreenWidth() {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
return metrics.widthPixels;
}
/**
* @return 屏幕高度
*/
public int getScreenHeight() {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
return metrics.heightPixels;
}
/**
* @return 返回窗口内容的宽度,不包括通知栏的标题栏,其实跟getScreenWidth()一样
*/
public int getWindowWidth() {
return getWindow().findViewById(Window.ID_ANDROID_CONTENT).getWidth();
}
/**
* @return 返回窗口内容的高度,不包括通知栏的标题栏,但是在这里是全屏,所以与getScreenHeight()返回的其实是一致的
*/
public int getWindowHeight() {
return getWindow().findViewById(Window.ID_ANDROID_CONTENT).getHeight();
}
/**
* 圆形
*/
public class Dot extends ImageView {
private int dotradius = 0;// 圆形半径
private boolean passed = false;// 是否经过的状态
public Dot(Context context) {
super(context);
}
public Dot(Context context, int rad) {
super(context);
dotradius = rad;
setLayoutParams(new LayoutParams(dotradius * 2, dotradius * 2));
drawNormal();
}
/**
* 绘制未经过时的状态
*/
public void drawNormal() {
passed = false;
Bitmap bm = Bitmap.createBitmap(dotradius * 2, dotradius * 2,
Config.ARGB_8888);
Paint paint = new Paint();
Canvas canvas = new Canvas(bm);
paint.setAntiAlias(true);
paint.setARGB(255, 156, 156, 156);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(5);
canvas.drawCircle(dotradius, dotradius,
dotradius - paint.getStrokeWidth(), paint);
paint.setStrokeWidth(1);
paint.setStyle(Style.FILL_AND_STROKE);
canvas.drawCircle(dotradius, dotradius, 3, paint);
setImageBitmap(bm);
}
/**
* 绘制经过时的状态
*/
public void drawPassed() {
passed = true;
Bitmap bm = Bitmap.createBitmap(dotradius * 2, dotradius * 2,
Config.ARGB_8888);
Paint paint = new Paint();
Canvas canvas = new Canvas(bm);
paint.setAntiAlias(true);
paint.setARGB(255, 0, 0, 255);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(5);
canvas.drawCircle(dotradius, dotradius,
dotradius - paint.getStrokeWidth(), paint);
paint.setStyle(Style.FILL_AND_STROKE);
canvas.drawCircle(dotradius, dotradius, dotradius / 3, paint);
setImageBitmap(bm);
}
public boolean getPassed() {
return passed;
}
}
}
布局xml代码,很简单: