先上效果图
效果如上,接下来是如何实现,并没有那么快上代码,不看代码不舒服的请迅速下翻。
九宫格解锁还是比较经典的,作为学习自定义view的入门。
对于九宫格解锁,我的实现思路是这样的:
1.先在屏幕上绘制一个正方形,就是上面草图中间的正方形。
2.将这个正方形纵向横向三等分,分为九个小正方形,分别序号1-9…
3.每个小正方形以1/3边长为半径画出圆,这时候我们已经有了基本的样子。
4.接下来是触屏事件的处理,把所有的触屏坐标减去大正方形的坐上点,就是以大正方形起始点为坐标原点。
private int whichPath(int x,int y){
if(x>startX+width | y>startY+width |x<startX |y<startY){
// Log.i("xy",x+"-"+y);
return 0;
}else {
//以起始点开始计算坐标
int nX=x-startX;
int nY=y-startY;
int hang=(int)nY/tWidth;//在哪行,从零开始
int lie=(int)nX/tWidth;//在哪列,从零开始
// Log.i("nXY",nX+"-"+nY);
int reInt= hang*3+1+lie;
//缩小判断区域
int smallY=(tWidth/6)+hang*tWidth;
int smallX=(tWidth/6)+lie*tWidth;
if (nX>smallX && nY>smallY && nX<(smallX+2*tWidth/3) && nY<(smallY+2*tWidth/3)){
return reInt;
}else{
return 0;
}
}
}//which
我用以上的方法判断坐标所在区域,并且把判断区域缩小范围,留出足够的空间进行斜向连线。
就是原来的范围为每个小正方形,我把他判定范围缩减到草图中一号位圆圈外的黑色方框以内。
5.当触屏移动,坐标改变。调用上面方法判断在哪个区域,如果进入了合法区域就记录在结果集list中。
不管坐标在不在合法区域,需要把list中的最后一个区域的圆心跟当前移动的点连接,画一条直线。
然后循环取出结果集的区域圆心,两两连线,把结果集中的所有圆圈变为红色。
6.最后,绘制解锁图案完成后提供接口回调。
思路看完,下面是主要代码:(为了方便给代码,我省略了attr的操作,可自行扩展)
package com.toxicant.hua.nicepathsunlock;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* Created by hua on 2016/1/31.
*/
public class NinePathsView extends View{
private Paint mPaint=new Paint();//画笔
private Paint mRedPaint=new Paint();
private int startX=0;//
private int startY=0;//
private Point touchPoint=new Point(0,0);//当前触控点
private int width=0;//大正方形的边长
private int tWidth=0;//九个小正方形的边长
private int nowP=0;//当前所在区域
private List<Point> pointList=new ArrayList<>();//存放圆心的list
private volatile List<Integer> resultList=new ArrayList<>();//存放结果的list
private boolean isUnLock=false;
private DrawFinishListener mListener;
//绘制完成的接口
interface DrawFinishListener{
void finish(List<Integer> resultList);
}
public NinePathsView(Context context) {
super(context);
}
public NinePathsView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NinePathsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//此处获取自定义属性
}
@Override
protected void onDraw(Canvas canvas) {
width=Math.min(getMeasuredHeight(),getMeasuredWidth());//获取正方形区域边长
// Log.i("width",""+width);
//获取起始绘制点
startY=(getMeasuredHeight()-width)/2;
startX=(getMeasuredWidth()-width)/2;
//绘制大正方形
mPaint.setColor(Color.WHITE);
canvas.drawRect(startX, startY, startX + width, startY + width, mPaint);
//设置画笔
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(3);
mRedPaint.setColor(Color.RED);
mRedPaint.setStyle(Paint.Style.STROKE);
mRedPaint.setStrokeWidth(3);
//三等分正方形
tWidth=width/3;
pointList.clear();
for(int j=0;j<3;j++){
int tY=startY+j*tWidth+tWidth/2;
for(int k=0;k<3;k++){
int tX=startX+k*tWidth+tWidth/2;
canvas.drawCircle(tX, tY, 2, mPaint);
//此处已连接的点要变色
canvas.drawCircle(tX, tY, tWidth / 3, mPaint);
pointList.add(new Point(tX,tY));//圆心存入list
}
}
//连线已连接的点
for (int i=1;i<resultList.size();i++){
int back =resultList.get(i-1);
Point backP=pointList.get(back-1);//上一个圆心
int now=resultList.get(i);
Point nowP=pointList.get(now-1);
canvas.drawLine(backP.x,backP.y,nowP.x,nowP.y,mRedPaint);
}
for (int i:resultList){
Point nowP=pointList.get(i-1);
canvas.drawCircle(nowP.x,nowP.y,tWidth / 3,mRedPaint);
}
//如果正在连线,把最后一个圆心点和现在触控的点连接
if(isUnLock){
int last=resultList.get(resultList.size()-1);
Point lastP=pointList.get(last-1);
canvas.drawLine(lastP.x,lastP.y,touchPoint.x,touchPoint.y,mRedPaint);
}
}//draw
@Override
public boolean onTouchEvent(MotionEvent event) {
int x= (int) event.getRawX();
int y= (int) event.getRawY();
//坐标转换
int[] location = new int[2] ;
getLocationInWindow(location); //获取在当前窗口内的绝对坐标
//getLocationOnScreen(location);//获取在整个屏幕内的绝对坐标
//location [0]--->x坐标,location [1]--->y坐标
x=x-location [0];
y=y-location [1];
// Log.e("绝对坐标",x+"--"+y);
int which=whichPath(x,y);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// Log.i("which","->"+which);
resultList.clear();//开始下一次绘制前清空结果集
isUnLock=true;
break;
case MotionEvent.ACTION_MOVE:
if (which!=nowP && which!=0){//移动到了另一个合法区域
nowP=which;//更改当前区域
if(!resultList.contains(which)){//确保没进入过此区域
resultList.add(which);
}
}
//记录当前触控点并请求重绘
touchPoint.set(x,y);
invalidate();
break;
case MotionEvent.ACTION_UP:
//完成绘制解锁图后
nowP=0;
isUnLock=false;
Log.i("result",resultList.toString());
if(mListener!=null){
mListener.finish(resultList);
}
rePlay();
break;
}
return true;
}//touch
private int whichPath(int x,int y){
if(x>startX+width | y>startY+width |x<startX |y<startY){
// Log.i("xy",x+"-"+y);
return 0;
}else {
//以起始点开始计算坐标
int nX=x-startX;
int nY=y-startY;
int hang=(int)nY/tWidth;//在哪行,从零开始
int lie=(int)nX/tWidth;//在哪列,从零开始
// Log.i("nXY",nX+"-"+nY);
int reInt= hang*3+1+lie;
//缩小判断区域
int smallY=(tWidth/6)+hang*tWidth;
int smallX=(tWidth/6)+lie*tWidth;
if (nX>smallX && nY>smallY && nX<(smallX+2*tWidth/3) && nY<(smallY+2*tWidth/3)){
return reInt;
}else{
return 0;
}
}
}//which
public void rePlay(){
resultList.clear();
isUnLock=false;
invalidate();
}//rePlay
void setDrawFinishListener(DrawFinishListener l){
this.mListener=l;
}
}//class
调用自定义view的方法依然是这样的:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.toxicant.hua.nicepathsunlock.MainActivity">
<com.toxicant.hua.nicepathsunlock.NinePathsView
android:id="@+id/view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true">
</com.toxicant.hua.nicepathsunlock.NinePathsView>
</RelativeLayout>
直接作为普通组件调用。
在代码中进行回调:
package com.toxicant.hua.nicepathsunlock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
import java.util.List;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NinePathsView mView= (NinePathsView) findViewById(R.id.view);
mView.setDrawFinishListener(new NinePathsView.DrawFinishListener() {
@Override
public void finish(List<Integer> resultList) {
Toast.makeText(MainActivity.this,resultList.toString(),Toast.LENGTH_SHORT).show();
}
});
}
}
回调的是一个List<Integer>类型,可以直接调用tostring来保存,比较密码,也可直接通过size来密码限制最小长度。