先上图:


Android图形解锁的绘制_canvas


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


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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <RelativeLayout
        android:id="@+id/rela"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <ImageView
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>