? ? ? ?onDraw這個方法在自定義中尤其重要濒旦,我們可以measure之后通過Canvas進(jìn)行繪制者春,九宮格解鎖這個View現(xiàn)在已經(jīng)被人臉跟指紋給替代了捌刮,但是做起來還是有點東西的浸间。
下面就是做這個View的思路:
- 九個格子的布局
- 格子之間的連線
- 格子之間的連線所要考慮的問題
一 . 格子布局
? ? ? ?之前寫過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ū)別:
實際區(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)控乾。