自定义View之图案解锁(手势密码)

图案解锁应用的越来越广泛,因为好奇所以,查了些资料自己也模拟了一个,有不对的地方,欢迎吐槽。

  • 1、首先我们可以知道我们没有这个现成的View, 所以需要自定义一个View:

    创建一个类继承View并实现构造方法

  • 2、创建一个Point的类,为啥不用系统的,因为:

    我们的这个点有三种状态:正常(即未绘制的时候)、已选中、不符合条件的状态,还有每个点有个属于自己的编号,为了将手势密码转为字符串去存储。

  • 3、进行初始化工作(我都在三个参数的构造方法中写的,并且一个和两个参数的都引用了三个参数的构造方法):

    1)要画界面肯定少不了画笔,那么我们可以初始化两个画笔,一个是画点,一个是画线
    2)初始化图案的九个点
    3)初始化,绘制点需要的图片(因为点我用图片了,线就是线)

  • 4、重写onMeasure、onLayout、onDraw方法,我这里只用到了onMeasure和onDraw

    在onMeasure方法中计算一下View的大小,我这里没有按照全屏去处理,我觉得这样更灵活布局,onDraw肯定要重写的,自定义View还不写onDraw那干啥

  • 5、在onDraw中画点和线,那么线是根据我们的手指滑动来画的,那么我们就要重写onTouchEvent方法

    onDraw方法中就是绘制点和线
    onTouchEvent方法处理down、move和up的三个动作,这里返回的是true,如果不是true,后续的move和up是不会执行的

  • 6、当手指按下的那一刻,我们怎么知道有没有点在点上即点在图片上

    借助两点之间的距离小于半径来判断

  • 7、零碎的知识点:
    我们要有个点的集合存放选中的点,如果选中过的就不要添加进去,没有选中的就添加,
    当手指抬起意味着绘制结束,所以判断是否符合要求(根据需求来定),我这里是4个以上的满足,要判断是正确的还是错误的,错误的就改变点的状态,正确就记录他们点的code.
    需要注意的是每次动作后要刷新界面,调用postInvalidate方法来调用onDraw.

大体说了一下,剩下的看代码吧:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.CountDownTimer;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import com.example.a_0102.mylearn.R;

import java.util.ArrayList;
import java.util.List;

/**
 * 图案解锁即手势密码
 */

public class GesturePwdView extends View {

    private Context context;
    private int interval = 50;//间隔图片之间的间隔
    private int bitmapR = 100;//图片的半径
    private Point points[][] = new Point[3][3];
    private Bitmap bitmapNormal, bitmapPass, bitmapError;//三种状态的图片
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//画圈的画笔(不要在onDraw方法中创建画笔)
    private Paint paintLine = new Paint(Paint.ANTI_ALIAS_FLAG);//画线的画笔
    private Point point;//当前点击的point
    private List<Point> pointList = new ArrayList<>();//选中的点
    private boolean isFinish;//是否结束绘画
    private int moveX, moveY;//手指移动的点
    private StringBuilder code = new StringBuilder();

    public GesturePwdView(Context context) {
        this(context, null);
    }

    public GesturePwdView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GesturePwdView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        //设置化画线的画笔
        paintLine.setColor(Color.parseColor("#ff8500"));
        paintLine.setStrokeWidth(10);
        //初始化点和资源
        initPoints();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = 4 * interval + 6 * bitmapR;
        //设置View的宽高
        setMeasuredDimension(width, width);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //画点
        drawBitmap(canvas);
        //画线
        drawLine(canvas);
    }

    //画线,两个点两个点的画
    private void drawLine(Canvas canvas) {
        if (pointList.size() > 0) {
            for (int i = 0; i < pointList.size(); i++) {
                if (i != pointList.size() - 1) {
                    Point a = pointList.get(i);
                    Point b = pointList.get(i + 1);
                    canvas.drawLine(a.getX(), a.getY(), b.getX(), b.getY(), paintLine);
                } else {
                    Point last = pointList.get(i);
                    canvas.drawLine(last.getX(), last.getY(), moveX, moveY, paintLine);
                }
            }
        }
    }

    /**
     * 画bitmap
     *
     * @param canvas
     */
    private void drawBitmap(Canvas canvas) {
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                Point pp = points[i][j];
                if (pp.getStatus() == Point.STATUS_NORMAL) {
                    //-bitmapR的目的是使绘画的起点为图片的圆点(如interval+bitmap=150,绘制的起点即左上角是(150,150),而我们想要的是(150,150)是图片的中点)
                    canvas.drawBitmap(bitmapNormal, pp.x - bitmapR, pp.y - bitmapR, paint);
                } else if (pp.getStatus() == Point.STATUS_PASS) {
                    canvas.drawBitmap(bitmapPass, pp.x - bitmapR, pp.y - bitmapR, paint);
                } else {
                    canvas.drawBitmap(bitmapError, pp.x - bitmapR, pp.y - bitmapR, paint);
                }

            }
        }

    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        moveX = (int) event.getX();
        moveY = (int) event.getY();
        isFinish = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                reset();
                point = checkSelectPoint(moveX, moveY);
                if (point != null && !checkPointAdd(point)) {
                    pointList.add(point);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                point = checkSelectPoint(moveX, moveY);
                if (point != null && !checkPointAdd(point)) {
                    pointList.add(point);
                }
                break;
            case MotionEvent.ACTION_UP:
                //手指抬起绘制结束
                isFinish = true;
                break;
        }

        if (isFinish && pointList.size() <= 9 && pointList.size() > 0) {
            //去除最后一个点的后面的线
            moveX = pointList.get(pointList.size() - 1).getX();
            moveY = pointList.get(pointList.size() - 1).getY();
            if (pointList.size() == 1) {
                Toast.makeText(context, "不构成图案重新开始", Toast.LENGTH_LONG).show();
                setError();
            } else if (pointList.size() > 1 && pointList.size() < 4) {
                Toast.makeText(context, "至少选4个不同的点", Toast.LENGTH_LONG).show();
                pointList.get(pointList.size() - 1).getY();
                setError();
            }else {//成功的图案,记录编号,方便存储
                for(Point point:pointList){
                    code.append(point.getCode());
                }
            }
        }
        //刷新界面(切记切记)
        postInvalidate();
        //一定是true
        return true;
    }

    /**
     * 重置界面
     */
    private void reset() {
        paintLine.setColor(Color.parseColor("#ff8533"));
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                points[i][j].setStatus(Point.STATUS_NORMAL);
            }
        }
        pointList.clear();
    }

    /**
     * 不符合条件设置为error状态
     */
    private void setError() {
        paintLine.setColor(Color.parseColor("#eeeeee"));
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                if(pointList.contains(points[i][j])){
                    points[i][j].setStatus(Point.STATUS_ERROR);
                }
            }
        }

        //错误后1秒恢复状态
        CountDownTimer countDownTimer = new CountDownTimer(1 * 1000, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {

            }

            @Override
            public void onFinish() {
                reset();
                postInvalidate();
            }
        }.start();
    }

    /**
     * 检测点是否已经被添加过
     *
     * @param point
     * @return
     */
    private boolean checkPointAdd(Point point) {
        for (Point p : pointList) {
            if (pointList.contains(point)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否选中返回选中的点
     * @param downX
     * @param downY
     * @return
     */
    private Point checkSelectPoint(int downX, int downY) {
        for (int i = 0; i < points.length; i++) {
            for (int j = 0; j < points[i].length; j++) {
                if (isSelect(downX, downY, points[i][j])) {
                    points[i][j].setStatus(Point.STATUS_PASS);
                    return points[i][j];
                }
            }
        }
        return null;
    }

    /**
     * 判断是否选中点
     *
     * @param downX 手指点下的X坐标
     * @param downY 手指点下的Y的坐标
     * @param point 9个点中的一个
     * @return true选中了点
     */
    private boolean isSelect(int downX, int downY, Point point) {
        if (Math.sqrt((double) ((downX - point.x) * (downX - point.x) + (downY - point.y) * (downY - point.y))) < bitmapR) {
            return true;
        }
        return false;
    }

    //初始化各个点,坐标根据自己的需求计算
    private void initPoints() {

        //第一排的三个点
        points[0][0] = new Point(interval + bitmapR, (int) interval + bitmapR);
        points[0][0].setCode(0);
        points[0][1] = new Point((int) (interval * 2 + bitmapR * 3), (int) interval + bitmapR);
        points[0][1].setCode(1);
        points[0][2] = new Point((int) (interval * 3 + bitmapR * 5), (int) interval + bitmapR);
        points[0][2].setCode(2);

        //第二排的三个点
        points[1][0] = new Point((int) interval + bitmapR, (int) (bitmapR * 3 + interval * 2));
        points[1][0].setCode(3);
        points[1][1] = new Point((int) (interval * 2 + bitmapR * 3), (int) (bitmapR * 3 + interval * 2));
        points[1][1].setCode(4);
        points[1][2] = new Point((int) (interval * 3 + bitmapR * 5), (int) (bitmapR * 3 + interval * 2));
        points[1][2].setCode(5);

        //第三排的三个点
        points[2][0] = new Point((int) interval + bitmapR, (int) (bitmapR * 5 + interval * 3));
        points[2][0].setCode(6);
        points[2][1] = new Point((int) (interval * 2 + bitmapR * 3), (int) (bitmapR * 5 + interval * 3));
        points[2][1].setCode(7);
        points[2][2] = new Point((int) (interval * 3 + bitmapR * 5), (int) (bitmapR * 5 + interval * 3));
        points[2][2].setCode(8);

        bitmapNormal = BitmapFactory.decodeResource(getResources(), R.drawable.gesture_node_normal);
        bitmapPass = BitmapFactory.decodeResource(getResources(), R.drawable.img_tab_course_s);
        bitmapError = BitmapFactory.decodeResource(getResources(), R.drawable.img_tab_course_n);

    }

    //自定义一个点的类,这个类有点的三种状态,正常,错误,和选中
    class Point {
        private static final int STATUS_NORMAL = 0;
        private static final int STATUS_PASS = 1;
        private static final int STATUS_ERROR = 2;
        private int status;//状态,是正常的还是选中的还是错误的
        private int code;//每个点有个编号,将手势密码转为数字密码存储方便
        private int x;
        private int y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getStatus() {
            return status;
        }

        public void setStatus(int status) {
            this.status = status;
        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }
    }
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.a_0102.mylearn.gesture.GesturePwdActivity">

    <com.example.a_0102.mylearn.gesture.GesturePwdView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/blue"
        android:layout_gravity="center">

    </com.example.a_0102.mylearn.gesture.GesturePwdView>

</LinearLayout>

示例图这里写图片描述

猜你喜欢

转载自blog.csdn.net/hello_1s/article/details/81172583
今日推荐