Android onDraw()--九宮格解鎖

? ? ? ?onDraw這個方法在自定義中尤其重要濒旦,我們可以measure之后通過Canvas進(jìn)行繪制者春,九宮格解鎖這個View現(xiàn)在已經(jīng)被人臉跟指紋給替代了捌刮,但是做起來還是有點東西的浸间。
下面就是做這個View的思路:

  1. 九個格子的布局
  2. 格子之間的連線
  3. 格子之間的連線所要考慮的問題

一 . 格子布局

? ? ? ?之前寫過onlayout來進(jìn)行View的排放恨憎,那要繼承于ViewGroup与境,這次我繼承之View來實現(xiàn)验夯。在onDraw中進(jìn)行位置的擺放。整個的思路就是整出一個正方形摔刁,然后均與擺放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);
    }

? ? ? ?這里默認(rèn)的是白色的趁俊,當(dāng)被選中的時候換成藍(lán)色的域仇。很簡單我們的九宮格就畫出來了,那么看看如何進(jìn)行交互的寺擂。

二. 格子之間的連線

? ? ? ?第一反應(yīng)是用canvas.drawLine()來實現(xiàn)暇务,實際操作并沒有什么用泼掠,因為當(dāng)畫到一個點的時候,出發(fā)點就是當(dāng)前的這個點了垦细,之前的線段用drawLine()并不好用繪制择镇,這里要用Path來實現(xiàn),通過LineTo到某個點然后drawPath():

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

onDraw中的剩余代碼括改,第一句表示的是畫線段腻豌,記錄已經(jīng)畫出的線段,第二個則是記錄畫出的點嘱能,這里固定(startX吝梅,startY),改變 (endX, endY)來實現(xiàn)伸縮的線段惹骂。
我們來看一下onTouch方法苏携。onTouch有Down, Move,Up,這三種是比較常用的方法,這個后面再進(jìn)行拓展对粪。

 @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()把所有點的狀態(tài)置成白色的狀態(tài)刑枝,再把path重置一下怖糊,int i = getPoint(x, y); 來計算一下當(dāng)前按下的這個點是不是在某個九宮格中的某個圓里面衡怀,這時候把當(dāng)前的點放進(jìn)selectList集合里面并且把這個點的狀態(tài)置成被選中的狀態(tài),這就成了我們可以記錄的畫出密碼的線路儡遮,也就是我們的密碼跪但。因為是第一次按下去,所以把當(dāng)前的startX峦萎,startY換成當(dāng)前選中的點,做為我們的出發(fā)點忆首。
后面緊跟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();

? ? ? ?這里我們做的就是移動到當(dāng)前的點我們就改成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()方法就是記錄就是計算當(dāng)前點距離是不是在某個我們畫的圓里面爱榔,分別到9個圓心的距離就可以判斷是不是在當(dāng)前這個圓里面,然后可以確定是點擊的第幾個圓糙及。后面我們進(jìn)行了一次判斷假如我們selectList中已經(jīng)有了某個點详幽,當(dāng)我們再滑動到他的時候不進(jìn)行任何操作。后面加了一個方法getMissNum這個方法是后面思考的時候加上的主要的實現(xiàn)就是看圖1跟圖2的區(qū)別:

圖1.png

圖2.png

實際區(qū)別就是能不能主動吸附到中間這個點的問題代碼:

   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轉(zhuǎn)換來看看某個點在不在這個Path區(qū)域中浸锨,后來實踐之后發(fā)現(xiàn)并不能做的出來唇聘,也把代碼貼出來:

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);
    //線條結(jié)束處繪制一個半圓
    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);
  }
 }

最后關(guān)于一些想使用的自定義屬性自己可以直接定義一下迟郎,包括圓的樣式什么的都是可以自己自定義搞一下的。這里就不寫了聪蘸。只是實現(xiàn)了一下這個簡單的功能宪肖,對onDraw()方法中畫圖有一定認(rèn)識表制。主要有畫circle,畫Path ,畫Line的實戰(zhàn)控乾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末么介,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蜕衡,更是在濱河造成了極大的恐慌壤短,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慨仿,死亡現(xiàn)場離奇詭異久脯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)镶骗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門桶现,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鼎姊,你說我怎么就攤上這事骡和。” “怎么了相寇?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵慰于,是天一觀的道長。 經(jīng)常有香客問我唤衫,道長婆赠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任佳励,我火速辦了婚禮休里,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赃承。我一直安慰自己妙黍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布瞧剖。 她就那樣靜靜地躺著拭嫁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抓于。 梳的紋絲不亂的頭發(fā)上做粤,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音捉撮,去河邊找鬼怕品。 笑死,一個胖子當(dāng)著我的面吹牛巾遭,可吹牛的內(nèi)容都是我干的堵泽。 我是一名探鬼主播修己,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迎罗!你這毒婦竟也來了睬愤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤纹安,失蹤者是張志新(化名)和其女友劉穎尤辱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厢岂,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡光督,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了塔粒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片结借。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卒茬,靈堂內(nèi)的尸體忽然破棺而出船老,到底是詐尸還是另有隱情,我是刑警寧澤圃酵,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布柳畔,位于F島的核電站,受9級特大地震影響郭赐,放射性物質(zhì)發(fā)生泄漏薪韩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一捌锭、第九天 我趴在偏房一處隱蔽的房頂上張望俘陷。 院中可真熱鬧,春花似錦观谦、人聲如沸拉盾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雷激,卻和暖如春替蔬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屎暇。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工承桥, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人根悼。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓凶异,卻偏偏與公主長得像蜀撑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剩彬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容