先上图:

其实很简单,不用过多解释,一点点注释就够了。

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代码,很简单: