Android自定義View--實(shí)現(xiàn)九宮格解鎖圖案

源碼下載

項(xiàng)目中需求用到圖案解鎖的功能人乓,就自己寫了類似的功能:
說(shuō)下思路:

  • 1.實(shí)現(xiàn)一個(gè)子類繼承View
  • 2.覆蓋onDrow()函數(shù),渲染圖像
  • 3.覆蓋onTouchEvent()函數(shù)
  • 4.監(jiān)聽(tīng)按下、移動(dòng),松開手指的動(dòng)作
  • 5.重新在onDrow()中渲染對(duì)應(yīng)的的圖像

在描述功能之前,看一下效果圖,理解起來(lái)會(huì)起到事半功倍的作用

整體效果圖.jpg

說(shuō)明

  • A柿汛、B、C辑奈、D苛茂、E、F鸠窗、G妓羊、H、I代表九個(gè)坐標(biāo)點(diǎn)
  • 左圖中的圓由兩個(gè)同心圓組成.
  • 中圖鏈接起來(lái)的圓由四個(gè)同心圓組成稍计,增加了兩個(gè)綠色的圓躁绸,最外層綠色的是空心圓,紅色連線是帶有寬度的直線.
  • 右圖線條由紅色條變成了綠色.

1.實(shí)現(xiàn)UnlockAppView類繼承View

實(shí)現(xiàn)左圖:九個(gè)點(diǎn)的坐標(biāo)臣嚣,圓的半徑及顏色净刮。
空心圓:同圓心不同半徑,繪制顏色不同
坐標(biāo)如何確定:由屏幕的寬高決定硅则,按照比例畫出的效果圖在各種屏幕中看起來(lái)協(xié)調(diào).
定義所需參數(shù):

//屏幕的寬度
private int width;
//屏幕的高度
private int height;
//大圓半徑
private float rH;
//小圓半徑
private int rM;
//A的坐標(biāo)
private float a1, b1;
//B的坐標(biāo)
private float a2, b2;
//C的坐標(biāo)
private float a3, b3;
//D的坐標(biāo)
private float a4, b4;
//E的坐標(biāo)
private float a5, b5;
//F的坐標(biāo)
private float a6, b6;
//G的坐標(biāo)
private float a7, b7;
//H的坐標(biāo)
private float a8, b8;
//I的坐標(biāo)
private float a9, b9;
//繪制大圓用到的畫筆
private Paint mPaint;
//繪制小圓用到的畫筆
private Paint mPaint0;

參數(shù)命名完成淹父,接下來(lái)開始賦值:

DisplayMetrics metric = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metric);
//獲取屏幕的寬度
width = metric.widthPixels;
//獲取屏幕的高度
height = metric.heightPixels;
//以下計(jì)算是根據(jù)屏幕調(diào)試出來(lái)的合理大小,不必深究
//計(jì)算大圓的半徑怎虫,
rH = (width / 3) / 5;
//計(jì)算小圓的半徑暑认,
rM = (width / 3) / 10;
//點(diǎn)A的橫坐標(biāo),及縱坐標(biāo)
a1 = (width / 3) / 2;
b1 = (width / 3) / 2 + (height - width) / 2;
//B點(diǎn)坐標(biāo)
a2 = (width / 3) + (width / 3) / 2;
b2 = b1;
//C點(diǎn)坐標(biāo)
a3 = (width / 3) * 2 + (width / 3) / 2;
b3 = b1;
//D點(diǎn)坐標(biāo)
a4 = a1;
b4 = (width / 3) + (width / 3) / 2 + (height - width) / 2;
//E點(diǎn)坐標(biāo)
a5 = a2;
b5 = b4;
//F點(diǎn)坐標(biāo)
a6 = a3;
b6 = b4;
//G點(diǎn)坐標(biāo)
a7 = a1;
b7 = (width / 3) * 2 + (width / 3) / 2 + (height - width) / 2;
//H點(diǎn)坐標(biāo)
a8 = a5;
b8 = b7;
//I點(diǎn)坐標(biāo)
a9 = a6;
b9 = b7;
//使位圖抗鋸齒
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//顏色,淺灰色
mPaint.setColor(Color.LTGRAY);
//使位圖抗鋸齒
mPaint0 = new Paint(Paint.ANTI_ALIAS_FLAG);
//顏色,白色
mPaint0 = new Paint(Paint.WHITE);

一切準(zhǔn)備就緒大审,重寫onDrow()函數(shù)蘸际,重新渲染

@Override
protected void onDraw(Canvas canvas) {
//每次繪制清空畫布
canvas.drawColor(Color.WHITE);

//渲染大圓,圓心(a1,b1)半徑rH,畫筆mPaint
canvas.drawCircle(a1, b1, rH, mPaint);
canvas.drawCircle(a2, b2, rH, mPaint);
canvas.drawCircle(a3, b3, rH, mPaint);
canvas.drawCircle(a4, b4, rH, mPaint);
canvas.drawCircle(a5, b5, rH, mPaint);
canvas.drawCircle(a6, b6, rH, mPaint);
canvas.drawCircle(a7, b7, rH, mPaint);
canvas.drawCircle(a8, b8, rH, mPaint);
canvas.drawCircle(a9, b9, rH, mPaint);
//渲染小圓
canvas.drawCircle(a1, b1, rM, mPaint0);
canvas.drawCircle(a2, b2, rM, mPaint0);
canvas.drawCircle(a3, b3, rM, mPaint0);
canvas.drawCircle(a4, b4, rM, mPaint0);
canvas.drawCircle(a5, b5, rM, mPaint0);
canvas.drawCircle(a6, b6, rM, mPaint0);
canvas.drawCircle(a7, b7, rM, mPaint0);
canvas.drawCircle(a8, b8, rM, mPaint0);
canvas.drawCircle(a9, b9, rM, mPaint0);
}

以上完成左圖的渲染徒扶。

實(shí)現(xiàn)中圖的效果

跟蹤手指劃過(guò)的痕跡
軌跡是否是否經(jīng)過(guò)圓的區(qū)域
說(shuō)明粮彤,這里圓的區(qū)域用圓的外切正方形的區(qū)域代替。
矩形對(duì)象的contains()方法可判斷軌跡經(jīng)過(guò)園的區(qū)域姜骡。
代碼實(shí)例
rt1.contains(tX, tY)

定義園的外切正方形變量

//九個(gè)正方形區(qū)域
//左上角坐標(biāo)(a1 - rH, b1 - rH)及右下角坐標(biāo)(a1 + rH, b1 + rH)
private RectF rt1 = 
new RectF(a1 - rH, b1 - rH, a1 + rH, b1 + rH);
private RectF rt2 = 
new RectF(a2 - rH, b2 - rH, a2 + rH, b2 + rH);
private RectF rt3 = new RectF(a3 - rH, b3 - rH, a3 + rH, b3 + rH);
private RectF rt4 = new RectF(a4 - rH, b4 - rH, a4 + rH, b4 + rH);
private RectF rt5 = new RectF(a5 - rH, b5 - rH, a5 + rH, b5 + rH);
private RectF rt6 = new RectF(a6 - rH, b6 - rH, a6 + rH, b6 + rH);
private RectF rt7 = new RectF(a7 - rH, b7 - rH, a7 + rH, b7 + rH);
private RectF rt8 = new RectF(a8 - rH, b8 - rH, a8 + rH, b8 + rH);
private RectF rt9 = new RectF(a9 - rH, b9 - rH, a9 + rH, b9 + rH);

使用invalidate()方法导坟,刷新整個(gè)畫布。
所以需要記錄軌跡經(jīng)過(guò)A圈澈、B惫周、C、D士败、E、F、G谅将、H漾狼、I九個(gè)點(diǎn)經(jīng)過(guò)的先后順序。

定義一個(gè)String變量passwordValue存儲(chǔ)經(jīng)過(guò)坐標(biāo)點(diǎn)的先后順序饥臂;
兩圓之間的紅色線段逊躁,passwordValue記錄著經(jīng)過(guò)的圓的先后順利,根據(jù)經(jīng)過(guò)圓的先后順利繪制線段隅熙;
例如:passwordValue ="ACDE"代表經(jīng)過(guò)的圓的順序圓A->圓C->圓D->圓E稽煤,繪制的線段AC、CD囚戚、DE酵熙;
線段是由起始坐標(biāo),終止坐標(biāo)表示驰坊,所以需要定義一個(gè)兩行兩列的二維數(shù)組用來(lái)存儲(chǔ)起始及終止坐標(biāo)匾二,第一行代表起始坐標(biāo),第二行代表終點(diǎn)坐標(biāo)拳芙;
由于每次刷新整個(gè)畫布察藐,需要把二維數(shù)據(jù)存儲(chǔ)在一個(gè)列表中,方便遍歷渲染舟扎;

圓與線段的渲染分開來(lái)講解分飞,先來(lái)看看圓的渲染過(guò)程,獲取手指滑動(dòng)坐標(biāo)睹限,重寫onTouchEvent方法

@Override
public boolean onTouchEvent(MotionEvent event)
//捕捉按下的動(dòng)作
if (event.getAction() == MotionEvent.ACTION_DOWN) {

} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
    //X坐標(biāo)點(diǎn)
    float tX = event.getX();
    //Y坐標(biāo)點(diǎn)
    float tY = event.getY();
    //首次經(jīng)過(guò)圓A
    if (rt1.contains(tX, tY) && !passwordValue.contains("A")) {
         passwordValue += "A";
    } else if (rt2.contains(tX, tY) && !passwordValue.contains("B")) {//首次經(jīng)過(guò)圓B
         passwordValue += "B";
    } else if (rt3.contains(tX, tY) && !passwordValue.contains("C")) {//首次經(jīng)過(guò)圓C
         passwordValue += "C";
    } else if (rt4.contains(tX, tY) && !passwordValue.contains("D")) {//首次經(jīng)過(guò)圓D
         passwordValue += "D";
    } else if (rt5.contains(tX, tY) && !passwordValue.contains("E")) {//首次經(jīng)過(guò)圓E
         passwordValue += "E";
    } else if (rt6.contains(tX, tY) && !passwordValue.contains("F")) {//首次經(jīng)過(guò)圓F
         passwordValue += "F";
    } else if (rt7.contains(tX, tY) && !passwordValue.contains("G")) {//首次經(jīng)過(guò)圓G
        passwordValue += "G";
    } else if (rt8.contains(tX, tY) && !passwordValue.contains("H")) {//首次經(jīng)過(guò)圓H
        passwordValue += "H";
    } else if (rt9.contains(tX, tY) && !passwordValue.contains("I")) {//首次經(jīng)過(guò)圓I
        passwordValue += "I";
    }
    invalidate();// 刷新畫布譬猫,回調(diào)onDraw()方法
}  else if (event.getAction() == MotionEvent.ACTION_UP)  {
        
}

確定了圓的順序,刷新畫布邦泄,渲染軌跡坐標(biāo)經(jīng)過(guò)的圓的效果及紅色直線的效果

protected void onDraw(Canvas canvas) {
...
...
//軌跡經(jīng)過(guò)圓A
if (passwordValue.contains("A")) {// (a1,b1)
     canvas.drawCircle(a1, b1, rL, mPaintOKM);
     canvas.drawCircle(a1, b1, rH, mPaintOKH);
}
//軌跡經(jīng)過(guò)圓B
if (passwordValue.contains("B")) {// (a2,b2)
     canvas.drawCircle(a2,b2, rL, mPaintOKM);
     canvas.drawCircle(a2,b2, rH, mPaintOKH);
}
//軌跡經(jīng)過(guò)圓C
if (passwordValue.contains("C")) {// (a3,b3)
     canvas.drawCircle(a3,b3, rL, mPaintOKM);
     canvas.drawCircle(a3,b3, rH, mPaintOKH);
}
//軌跡經(jīng)過(guò)圓D
if (passwordValue.contains("D")) {// (a4,b4)
     canvas.drawCircle(a4,b4, rL, mPaintOKM);
     canvas.drawCircle(a4,b4, rH, mPaintOKH);
}
//軌跡經(jīng)過(guò)圓E
if (passwordValue.contains("E")) {// (a5,b5)
     canvas.drawCircle(a5,b5, rL, mPaintOKM);
     canvas.drawCircle(a5,b5, rH, mPaintOKH);
}
//軌跡經(jīng)過(guò)圓F
if (passwordValue.contains("F")) {// (a6,b6)
     canvas.drawCircle(a6,b6, rL, mPaintOKM);
     canvas.drawCircle(a6,b6, rH, mPaintOKH);
}
//軌跡經(jīng)過(guò)圓G
if (passwordValue.contains("G")) {// (a7,b7)
     canvas.drawCircle(a7,b7, rL, mPaintOKM);
     canvas.drawCircle(a7,b7, rH, mPaintOKH);
}
//軌跡經(jīng)過(guò)圓H
if (passwordValue.contains("H")) {// (a8,b8)
     canvas.drawCircle(a8,b8, rL, mPaintOKM);
     canvas.drawCircle(a8,b8, rH, mPaintOKH);
}
//軌跡經(jīng)過(guò)圓I
if (passwordValue.contains("I")) {// (a9,b9)
     canvas.drawCircle(a9,b9, rL, mPaintOKM);
     canvas.drawCircle(a9,b9, rH, mPaintOKH);
}

線段的渲染過(guò)程删窒,獲取線段的端點(diǎn)坐標(biāo),重寫onTouchEvent方法

//存儲(chǔ)線段起始及終止坐標(biāo)的二維數(shù)組
float[][] lineCoordinate = new float[2][2]
//存儲(chǔ)二維數(shù)據(jù)的列表
List<Float[][]> listCoordinate = new ArrayList();
//經(jīng)過(guò)圓的數(shù)量顺囊,num < 4 線段顏色為紅色 num >= 4線段顏色為綠色
int num = 0;

@Override
public boolean onTouchEvent(MotionEvent event) 
if (event.getAction() == MotionEvent.ACTION_DOWN) {

} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
   //X坐標(biāo)點(diǎn)
    float tX = event.getX();
    //Y坐標(biāo)點(diǎn)
    float tY = event.getY();
    //首次經(jīng)過(guò)圓A
    if (rt1.contains(tX, tY) && !passwordValue.contains("A")) {
         passwordValue += "A";
         //num經(jīng)過(guò)的圓的數(shù)量
         //num != 0代表不是第一個(gè)經(jīng)過(guò)的圓肌索,第一個(gè)經(jīng)過(guò)的圓只能是線段的起始坐標(biāo)不能是線段的終止坐標(biāo)
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a1, b1};    
            //存儲(chǔ)線段起及始終止坐標(biāo)的二維數(shù)組存儲(chǔ)到列表中
            listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a1, b1};
         num += 1;
    } else if (rt2.contains(tX, tY) && !passwordValue.contains("B")) {//首次經(jīng)過(guò)圓B
         passwordValue += "B";
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a2, b2};    
             listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a2, b2};
        num += 1;
    } else if (rt3.contains(tX, tY) && !passwordValue.contains("C")) {//首次經(jīng)過(guò)圓C
         passwordValue += "C";
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a3, b3};    
             listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a3, b3};
        num += 1;
    } else if (rt4.contains(tX, tY) && !passwordValue.contains("D")) {//首次經(jīng)過(guò)圓D
         passwordValue += "D";
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a4, b4};    
             listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a4, b4};
        num += 1;
    } else if (rt5.contains(tX, tY) && !passwordValue.contains("E")) {//首次經(jīng)過(guò)圓E
         passwordValue += "E";
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a5, b5};    
             listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a5, b5};
        num += 1;
    } else if (rt6.contains(tX, tY) && !passwordValue.contains("F")) {//首次經(jīng)過(guò)圓F
         passwordValue += "F";
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a6, b6};    
             listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a6, b6};
        num += 1;
    } else if (rt7.contains(tX, tY) && !passwordValue.contains("G")) {//首次經(jīng)過(guò)圓G
        passwordValue += "G";
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a7, b7};    
             listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a7, b7};
        num += 1;
    } else if (rt8.contains(tX, tY) && !passwordValue.contains("H")) {//首次經(jīng)過(guò)圓H
        passwordValue += "H";
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a8, b8};    
             listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a8, b8};
        num += 1;
    } else if (rt9.contains(tX, tY) && !passwordValue.contains("I")) {//首次經(jīng)過(guò)圓I
        passwordValue += "I";
         if (num != 0) {      
            //線段的終止坐標(biāo)  
             fts[1] = new float[]{a9, b9};    
             listCoordinate.add(fts);
         }
        //初始化存儲(chǔ)線段坐標(biāo)的二維數(shù)組
         fts = new float[2][2];
        //線段的起始坐標(biāo)
        fts[0] = new float[]{a9, b9};
        num += 1;
    }
    invalidate();// 刷新畫布,回調(diào)onDraw()方法
} else if (event.getAction() == MotionEvent.ACTION_UP) {

}

刷新畫布特碳,渲染紅色線段诚亚,

//初始化渲染紅色線段畫筆
//Paint.ANTI_ALIAS_FLAG使圖像抗鋸齒
mPaintCancelM = new Paint(Paint.ANTI_ALIAS_FLAG)
//顏色紅色
mPaintCancelM.setColor(Color.RED);
//畫筆的寬度
mPaintCancelM.setStrokeWidth(rM);

protected void onDraw(Canvas canvas) {
...
...
for (int i = 0; i < listCoordinate.size(); i++) {
      float[][] lineCoordinate = listCoordinate.get(i);
      float startX = lineCoordinate[0][0];
      float startY = lineCoordinate[0][1];
      float stopX = lineCoordinate[1][0];
      float stopY = lineCoordinate[1][1];
     //渲染紅色線段
     canvas.drawLine(startX, startY, stopX, stopY, mPaintCancelM)
}
...
...

右圖跟中圖的渲染過(guò)程一樣,區(qū)別在于經(jīng)過(guò)的圓的數(shù)量大于等于4午乓,畫筆的顏色設(shè)置成綠色

至此站宗,以上左中右圖的渲染實(shí)現(xiàn)過(guò)程完畢,但還有兩個(gè)中間狀態(tài)

不完整的線段效果圖.jpg

右圖跟左圖的渲染過(guò)程一樣,講解左圖的實(shí)現(xiàn)過(guò)程益愈,我們稱該狀態(tài)線段為不完整線段梢灭,以區(qū)分之前的線段夷家。

手指滑動(dòng)未到達(dá)圓所在的區(qū)域時(shí),線段的起始坐標(biāo)是軌跡經(jīng)過(guò)的最后一個(gè)圓的圓心坐標(biāo)敏释,我們只需記錄終點(diǎn)坐標(biāo)就可實(shí)現(xiàn)以上圖中的狀態(tài)库快。

//存儲(chǔ)不完整線段起始終止坐標(biāo)的二維數(shù)組
float[][] lineCrdinateImperfect = new float[2][2]
lineCrdinateImperfect[0] = new float[2];
lineCrdinateImperfect[1] = new float[2];
@Override
public boolean onTouchEvent(MotionEvent event) {    
if (event.getAction() == MotionEvent.ACTION_DOWN) {

} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
       float tX = event.getX();
       float tY = event.getY();
       //不完整線段終點(diǎn)坐標(biāo)賦值
       lineCrdinateImperfect[1] [0] = tX;
       lineCrdinateImperfect[1] [1] = tY;
   //首次經(jīng)過(guò)圓A
    if (rt1.contains(tX, tY) && !passwordValue.contains("A")) {
         passwordValue += "A";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a1;
       lineCrdinateImperfect[0] [1] = b1;
    } else if (rt2.contains(tX, tY) && !passwordValue.contains("B")) {//首次經(jīng)過(guò)圓B
         passwordValue += "B";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a2;
       lineCrdinateImperfect[0] [1] = b2;
    } else if (rt3.contains(tX, tY) && !passwordValue.contains("C")) {//首次經(jīng)過(guò)圓C
         passwordValue += "C";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a3;
       lineCrdinateImperfect[0] [1] = b3;
    } else if (rt4.contains(tX, tY) && !passwordValue.contains("D")) {//首次經(jīng)過(guò)圓D
         passwordValue += "D";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a4;
       lineCrdinateImperfect[0] [1] = b4;
    } else if (rt5.contains(tX, tY) && !passwordValue.contains("E")) {//首次經(jīng)過(guò)圓E
         passwordValue += "E";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a5;
       lineCrdinateImperfect[0] [1] = b5;
    } else if (rt6.contains(tX, tY) && !passwordValue.contains("F")) {//首次經(jīng)過(guò)圓F
         passwordValue += "F";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a6;
       lineCrdinateImperfect[0] [1] = b6;
    } else if (rt7.contains(tX, tY) && !passwordValue.contains("G")) {//首次經(jīng)過(guò)圓G
        passwordValue += "G";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a7;
       lineCrdinateImperfect[0] [1] = b7;
    } else if (rt8.contains(tX, tY) && !passwordValue.contains("H")) {//首次經(jīng)過(guò)圓H
        passwordValue += "H";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a8;
       lineCrdinateImperfect[0] [1] = b8;
    } else if (rt9.contains(tX, tY) && !passwordValue.contains("I")) {//首次經(jīng)過(guò)圓I
        passwordValue += "I";
       //不完整線段起始坐標(biāo)賦值
       lineCrdinateImperfect[0] [0] = a9;
       lineCrdinateImperfect[0] [1] = b9;
    }
    invalidate();// 刷新畫布,回調(diào)onDraw()方法
} else if (event.getAction() == MotionEvent.ACTION_UP) {

}

刷新畫布钥顽,渲染不完整線段

@Override
protected void onDraw(Canvas canvas) {
...
...
      //不完整線段坐標(biāo)賦值
      float startXImperfect = lineCrdinateImperfect[0][0];
      float startYImperfect = lineCrdinateImperfect[0][1];
      float stopXImperfect= lineCrdinateImperfect[1][0];
      float stopYImperfect = lineCrdinateImperfect[1][1];
      //渲染不完整直線
      canvas.drawLine(startXImperfect, startYImperfect, stopXImperfect, stopYImperfect, mPaintCancelM);
...
...
}

注意:從一個(gè)圓(A)出發(fā)义屏,繞過(guò)一個(gè)圓(B),到達(dá)圓另一個(gè)圓(C)蜂大,這樣會(huì)忽略中間的圓(B)闽铐,經(jīng)過(guò)的圓的順序A->C,這樣不合理奶浦,明明經(jīng)過(guò)了中間圓(B)兄墅,軌跡應(yīng)該是A->B->C才對(duì)。
解決思路:計(jì)算兩圓心坐標(biāo)中點(diǎn)坐標(biāo)是否為其他圓的圓心坐標(biāo)财喳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末察迟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子耳高,更是在濱河造成了極大的恐慌扎瓶,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泌枪,死亡現(xiàn)場(chǎng)離奇詭異概荷,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)碌燕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門误证,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人修壕,你說(shuō)我怎么就攤上這事愈捅。” “怎么了慈鸠?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蓝谨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我青团,道長(zhǎng)譬巫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任督笆,我火速辦了婚禮芦昔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘娃肿。我一直安慰自己咕缎,他們只是感情好珠十,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著凭豪,像睡著了一般宵睦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上墅诡,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音桐智,去河邊找鬼末早。 笑死,一個(gè)胖子當(dāng)著我的面吹牛说庭,可吹牛的內(nèi)容都是我干的然磷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼刊驴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼姿搜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起捆憎,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舅柜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后躲惰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體致份,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年础拨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了氮块。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诡宗,死狀恐怖滔蝉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情塔沃,我是刑警寧澤蝠引,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站芳悲,受9級(jí)特大地震影響立肘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜名扛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一谅年、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肮韧,春花似錦融蹂、人聲如沸旺订。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)区拳。三九已至,卻和暖如春意乓,著一層夾襖步出監(jiān)牢的瞬間樱调,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工届良, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笆凌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓士葫,卻偏偏與公主長(zhǎng)得像乞而,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子慢显,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

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