Android onDraw()--九宫格解锁

       onDraw这个方法在自定义中尤其重要,我们可以measure之后通过Canvas进行绘制,九宫格解锁这个View现在已经被人脸跟指纹给替代了,但是做起来还是有点东西的。
下面就是做这个View的思路:

  1. 九个格子的布局
  2. 格子之间的连线
  3. 格子之间的连线所要考虑的问题

一 . 格子布局

       之前写过onlayout来进行View的排放,那要继承于ViewGroup,这次我继承之View来实现。在onDraw中进行位置的摆放。整个的思路就是整出一个正方形,然后均与摆放9个格子的位置。本次摆放的方法是摆放一个5*5的矩形,即两个圆之间的间隔也是一个圆,这样方便了后期的计算。首先是计算每个球的圆心:

    radius = (measureWidth - 100) / 5;
    innerPadding = 50;
    radius = radius / 2;
    int centerX = innerPadding + radius, centerY = innerPadding + radius;
    for (int i = 0; i < 9; i++) {
        pointList.add(new Point(centerX, centerY));
        centerX += 4 * radius;
        if (centerX > (measureWidth - 100)) {
            centerX = innerPadding + radius;
            centerY += 4 * radius;
        }
    }

       这里的操作就跟之前的Android onLayout()摆放位置是一个道理,然后在onDraw()中:

   for (Point mPoint : pointList) {
         if (mPoint.isSelected) {
            paint.setColor(Color.BLUE);
        } else {
            paint.setColor(Color.WHITE);
        }
        canvas.drawCircle(mPoint.x, mPoint.y, radius, paint);
    }

       这里默认的是白色的,当被选中的时候换成蓝色的。很简单我们的九宫格就画出来了,那么看看如何进行交互的。

二. 格子之间的连线

       第一反应是用canvas.drawLine()来实现,实际操作并没有什么用,因为当画到一个点的时候,出发点就是当前的这个点了,之前的线段用drawLine()并不好用绘制,这里要用Path来实现,通过LineTo到某个点然后drawPath():

    canvas.drawPath(path, linePaint);
    canvas.drawLine(startX, startY, endX, endY, linePaint);

onDraw中的剩余代码,第一句表示的是画线段,记录已经画出的线段,第二个则是记录画出的点,这里固定(startX,startY),改变 (endX, endY)来实现伸缩的线段。
我们来看一下onTouch方法。onTouch有Down, Move,Up,这三种是比较常用的方法,这个后面再进行拓展。

 @Override
 public boolean onTouchEvent(MotionEvent event) {
     switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return true;
}

看一下Down里面的方法:

            setAllunSelected();
            path.reset();
            int i = getPoint(x, y);
            if (DEFAULT_NUM == i) {
                invalidate();
                return false;
            } else {
                //说明点在点子上了
                selectList.add(i);
                pointList.get(i).setSelected(true);
                path.moveTo(pointList.get(i).getX(), pointList.get(i).getY());
                startX = pointList.get(i).getX();
                startY = pointList.get(i).getY();
            }

       首先把之前的都清除掉用setAllunSelected()把所有点的状态置成白色的状态,再把path重置一下,int i = getPoint(x, y); 来计算一下当前按下的这个点是不是在某个九宫格中的某个圆里面,这时候把当前的点放进selectList集合里面并且把这个点的状态置成被选中的状态,这就成了我们可以记录的画出密码的线路,也就是我们的密码。因为是第一次按下去,所以把当前的startX,startY换成当前选中的点,做为我们的出发点。
后面紧跟onMove代码:

            endX = x;
            endY = y;
            int j = getPoint(x, y);
            if (DEFAULT_NUM != j) {
                if(selectList.contains(j)){
                    invalidate();
                    break;
                }
                int missNum = getPointNum(j);
                if(missNum!=DEFAULT_NUM && !selectList.contains(j) ){
                    selectList.add(missNum);
                    pointList.get(missNum).setSelected(true);
                }
                selectList.add(j);
                pointList.get(j).setSelected(true);
                path.lineTo(pointList.get(j).getX(), pointList.get(j).getY());
                startX = pointList.get(j).getX();
                startY = pointList.get(j).getY();
            }
            invalidate();

       这里我们做的就是移动到当前的点我们就改成endX,endY,还是通过getPoint这个方法来判断是不是按在某个点里面:

private int getPoint(float x, float y) {
    for (int i = 0; i < pointList.size(); i++) {
        int j = getPosition(x, y, i);
        if (DEFAULT_NUM != j) {
            return j;
        }
    }
    return DEFAULT_NUM;
}

private int getPosition(float x, float y, int position) {
    Point point = pointList.get(position);
    if (Math.hypot(Math.abs(x - point.x), Math.abs(y - point.y)) < radius) {
        return position;
    }
    return DEFAULT_NUM;
}

三.连线要考虑的问题

       getPoint()方法就是记录就是计算当前点距离是不是在某个我们画的圆里面,分别到9个圆心的距离就可以判断是不是在当前这个圆里面,然后可以确定是点击的第几个圆。后面我们进行了一次判断假如我们selectList中已经有了某个点,当我们再滑动到他的时候不进行任何操作。后面加了一个方法getMissNum这个方法是后面思考的时候加上的主要的实现就是看图1跟图2的区别:

图1.png

图2.png

实际区别就是能不能主动吸附到中间这个点的问题代码:

   private  int  getPointNum(int position){
    if(selectList.size()<1)
    {
        return DEFAULT_NUM;
    }
    int size  =selectList.size();
    int i = selectList.get(size-1);
    Point p =  pointList.get(position);
    Point point = pointList.get(i);
    //判断是不是由上往下,从左往右
    boolean b  = false ;
    if(position>i){
        b = true;
    }
    if(Math.abs(p.getX()-point.getX())!=4*radius ||Math.abs(p.getX()-point.getX())!=4*radius){
        //说明不是相邻的两个点
        if(Math.abs(p.getX()-point.getX()) == 0 ||Math.abs(p.getY()-point.getY())==0 ){
            //说明一排或者一列中间有个没点到的
            if(Math.abs(p.getY()-point.getY()) == 0) {
                if(b){
                    return i+1;
                }else{
                    return i-1;
                }
            }else {
                if(b){
                    return i+3;
                }else{
                    return i-3;
                }
            }
        }else if(Math.abs(p.getX()-point.getX())==8*radius && Math.abs(p.getY()-point.getY())==8*radius){
           //这里是对角线
         return CENTER_NUM;
        }
    }
    return DEFAULT_NUM;
}

本来想通过Path转换来看看某个点在不在这个Path区域中,后来实践之后发现并不能做的出来,也把代码贴出来:

private boolean pointInPath(Path path, Point point) {
    RectF bounds = new RectF();
    path.computeBounds(bounds, true);
    Region region = new Region();
    region.setPath(path, new Region((int) bounds.left, (int) bounds.top,
   (int) bounds.right, (int) bounds.bottom));
    return region.contains((int)point.x, (int)point.y);
}

       最后就是将绘制好的密码扔出去,可以的话再添加个回掉把密码组给扔出去。
       完整代码:
public class LockView extends View {

final int DEFAULT_NUM = -1;
final int CENTER_NUM = 4;
String TAG = "LockView";
int measureWidth;
private int innerPadding;
private int radius;
List<Point> pointList;
Paint paint;
Paint linePaint;
float startX, startY;
float endX, endY;
Path path;
ArrayList<Integer> selectList;
boolean dismiss = false;

boolean isMove = false;

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

public LockView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public LockView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    pointList = new ArrayList<>();
    init();

}


private void init() {
    paint = new Paint();
    paint.setStrokeWidth(2);
    paint.setStyle(Paint.Style.FILL);
    paint.setAntiAlias(true);
    paint.setColor(Color.WHITE);

    linePaint = new Paint();
    linePaint.setStrokeWidth(20);
    linePaint.setStyle(Paint.Style.STROKE);
    linePaint.setAntiAlias(true);
    linePaint.setColor(Color.WHITE);
    linePaint.setStrokeJoin(Paint.Join.ROUND);
    //线条结束处绘制一个半圆
    linePaint.setStrokeCap(Paint.Cap.ROUND);
    selectList = new ArrayList<>();
    path = new Path();

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int size = Math.min(width, height);
    setMeasuredDimension(size, size);
    measureWidth = getMeasuredWidth();
}


@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    Log.d(TAG, "onSizeChanged: " + measureWidth);
    radius = (measureWidth - 100) / 5;
    innerPadding = 50;
    radius = radius / 2;
    int centerX = innerPadding + radius, centerY = innerPadding + radius;
    for (int i = 0; i < 9; i++) {
        pointList.add(new Point(centerX, centerY));
        centerX += 4 * radius;
        if (centerX > (measureWidth - 100)) {
            centerX = innerPadding + radius;
            centerY += 4 * radius;
        }
    }
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if(dismiss){
        setAllunSelected();
        path.reset();
    }
    for (Point mPoint : pointList) {
        if (mPoint.isSelected) {
            paint.setColor(Color.BLUE);
        } else {
            paint.setColor(Color.WHITE);
        }
        canvas.drawCircle(mPoint.x, mPoint.y, radius, paint);
    }

        canvas.drawPath(path, linePaint);
        canvas.drawLine(startX, startY, endX, endY, linePaint);

}

@Override
public boolean onTouchEvent(MotionEvent event) {
    float x = event.getX();
    float y = event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            setAllunSelected();
            dismiss = false;
            path.reset();
            int i = getPoint(x, y);
            if (DEFAULT_NUM == i) {
                invalidate();
                return false;
            } else {
                //说明点在点子上了
                selectList.add(i);
                pointList.get(i).setSelected(true);
                path.moveTo(pointList.get(i).getX(), pointList.get(i).getY());

                startX = pointList.get(i).getX();
                startY = pointList.get(i).getY();
            }
            break;
        case MotionEvent.ACTION_MOVE:
            endX = x;
            endY = y;
            int j = getPoint(x, y);
            if (DEFAULT_NUM != j) {
                if(selectList.contains(j)){
                    invalidate();
                    break;
                }
                int missNum = getPointNum(j);
                if(missNum!=DEFAULT_NUM && !selectList.contains(j) ){
                    selectList.add(missNum);
                    pointList.get(missNum).setSelected(true);
                }
                selectList.add(j);
                pointList.get(j).setSelected(true);
                path.lineTo(pointList.get(j).getX(), pointList.get(j).getY());
                startX = pointList.get(j).getX();
                startY = pointList.get(j).getY();
            }
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            endX = startX;
            endY = startY;
            dismiss = true ;
            invalidate();
            break;
        default:
            break;
    }
    return true;
}


private int getPoint(float x, float y) {
    for (int i = 0; i < pointList.size(); i++) {
        int j = getPosition(x, y, i);
        if (DEFAULT_NUM != j) {
            return j;
        }
    }
    return DEFAULT_NUM;
}


private int getPosition(float x, float y, int position) {
    Point point = pointList.get(position);
    if (Math.hypot(Math.abs(x - point.x), Math.abs(y - point.y)) < radius) {
        return position;
    }
    return DEFAULT_NUM;
}


class Point {

    float x;
    float y;
    boolean isSelected;

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

    public float getX() {
        return x;
    }

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

    public float getY() {
        return y;
    }

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

    public boolean isSelected() {
        return isSelected;
    }

    public void setSelected(boolean selected) {
        isSelected = selected;
    }
}


private void setAllunSelected() {
    for (int i = 0; i < pointList.size(); i++) {
        pointList.get(i).setSelected(false);
    }
    selectList.clear();
}



private  int  getPointNum(int position){
    if(selectList.size()<1)
    {
        return DEFAULT_NUM;
    }
    int size  =selectList.size();
    int i = selectList.get(size-1);
    Point p =  pointList.get(position);
    Point point = pointList.get(i);
    //判断是不是由上往下,从左往右
    boolean b  = false ;
    if(position>i){
        b = true;
    }
    if(Math.abs(p.getX()-point.getX())!=4*radius ||Math.abs(p.getX()-point.getX())!=4*radius){
        //说明不是相邻的两个点
        if(Math.abs(p.getX()-point.getX()) == 0 ||Math.abs(p.getY()-point.getY())==0 ){
            //说明一排或者一列中间有个没点到的
            if(Math.abs(p.getY()-point.getY()) == 0) {
                if(b){
                    return i+1;
                }else{
                    return i-1;
                }
            }else {
                if(b){
                    return i+3;
                }else{
                    return i-3;
                }
            }
        }else if(Math.abs(p.getX()-point.getX())==8*radius && Math.abs(p.getY()-point.getY())==8*radius){
           //这里是对角线
         return CENTER_NUM;
        }
    }
    return DEFAULT_NUM;
}
private boolean pointInPath(Path path, Point point) {
    RectF bounds = new RectF();
    path.computeBounds(bounds, true);
    Region region = new Region();
    region.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom));
    return region.contains((int)point.x, (int)point.y);
  }
 }

最后关于一些想使用的自定义属性自己可以直接定义一下,包括圆的样式什么的都是可以自己自定义搞一下的。这里就不写了。只是实现了一下这个简单的功能,对onDraw()方法中画图有一定认识。主要有画circle,画Path ,画Line的实战。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容