周末闲着没事,写了个手势解锁的view,实现起来也蛮快的,半天多一点时间就完事。把源码和资源贴出来,给大家分享,希望对大家有用。
效果,就跟手机上的九点手势解锁一样,上个图吧:
过程嘛感觉确实没啥好讲的了,涉及的知识以前的博客都说过了,无非就是canva,paint,touch事件这些,画画圆圈画画线条,剩下的就是细节处理逻辑了。都在代码里,所以这里就主要是贴资源吧。
这个自定义view就一个类,源码如下:
package com.cc.library.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.location.Location;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
/**
* 图案解锁view
* Created by zhangyu on 2016-07-15 15:05.
*/
public class UnlockView extends View {
private static final String TAG = "UnlockView";
//view宽高
private float width, height;
//平均宽高(分三份)
private float averageWidth, averageHeight;
//九个点的位置数据,从左到右、从上到下 123...789
Location[] locations = new Location[9];
//圆圈半径
private float radius;
//绘制密码
private int[] drawingPwd = new int[9];
//正确的密码
private int[] rightPwd;
//画笔
private Paint whitePaint, cyanPaint;
//已经绘制过了的点个数
private int drawedNumber;
//当前正被触摸的点
private Location nowTouchedPosition = new Location();
//监听
private UnlockListener unlockListener;
public void setUnlockListener(UnlockListener unlockListener) {
this.unlockListener = unlockListener;
}
public UnlockView(Context context) {
super(context);
init();
}
public UnlockView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public UnlockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
ViewTreeObserver vto = getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
height = getHeight();
width = getWidth();
Log.d(TAG, "width = " + width + " ,height = " + height);
averageWidth = width / 3f;
averageHeight = height / 3f;
radius = averageHeight > averageWidth ? averageWidth / 5f : averageHeight / 5f;
initLocation();
invalidate();
}
});
whitePaint = new Paint();
whitePaint.setAntiAlias(true);
whitePaint.setColor(Color.parseColor("#ffffff"));
whitePaint.setStyle(Paint.Style.STROKE);
cyanPaint = new Paint();
cyanPaint.setAntiAlias(true);
cyanPaint.setColor(Color.parseColor("#4169E1"));
cyanPaint.setStyle(Paint.Style.STROKE);
initDrawingPwd();
}
private void drawStart() {
drawedNumber = 0;
}
private void drawOver() {
//debug
StringBuffer sb = new StringBuffer();
for (int i = 0; i < drawingPwd.length; i++) {
sb.append(drawingPwd[i] + ",");
}
Log.i(TAG, "drawingPwd:" + sb.toString());
initLocation();
initDrawingPwd();
drawedNumber = 0;
invalidate();
}
/**
* 初始化绘制密码
*/
private void initDrawingPwd() {
for (int i = 0; i < 9; i++) {
drawingPwd[i] = -1;
}
}
/**
* 初始化九个点坐标
*/
private void initLocation() {
for (int i = 0; i < 9; i++) {
locations[i] = new Location();
locations[i].deawed = false;
//纵向1、2、3列x坐标
if (i % 3 == 0) {
locations[i].x = averageWidth * 0.5f;
} else if (i % 3 == 1) {
locations[i].x = averageWidth * 1.5f;
} else if (i % 3 == 2) {
locations[i].x = averageWidth * 2.5f;
}
//横向1、2、3排y坐标
if (i / 3 == 0) {
locations[i].y = averageHeight * 0.5f;
} else if (i / 3 == 1) {
locations[i].y = averageHeight * 1.5f;
} else if (i / 3 == 2) {
locations[i].y = averageHeight * 2.5f;
}
}
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < 9; i++) {
if (!locations[i].deawed) {//没被画
whitePaint.setStrokeWidth(4);
canvas.drawPoint(locations[i].x, locations[i].y, whitePaint);
whitePaint.setStrokeWidth(1.5f);
canvas.drawCircle(locations[i].x, locations[i].y, radius, whitePaint);
} else {//被画过了
cyanPaint.setStrokeWidth(8);
canvas.drawPoint(locations[i].x, locations[i].y, cyanPaint);
cyanPaint.setStrokeWidth(3f);
canvas.drawCircle(locations[i].x, locations[i].y, radius, cyanPaint);
}
int lastestDrawedPoint = -1;
if (drawedNumber > 0)
lastestDrawedPoint = drawingPwd[drawedNumber - 1];
if (lastestDrawedPoint != -1) {
Location lastestDrawedLocation = locations[lastestDrawedPoint];//最新一个被选中的点
cyanPaint.setStrokeWidth(3f);
canvas.drawLine(lastestDrawedLocation.x, lastestDrawedLocation.y, nowTouchedPosition.x, nowTouchedPosition.y, cyanPaint);
}
if (drawedNumber > 1) {
for (int j = 0; j < drawedNumber - 1; j++) {
cyanPaint.setStrokeWidth(3f);
canvas.drawLine(locations[drawingPwd[j]].x, locations[drawingPwd[j]].y, locations[drawingPwd[j + 1]].x, locations[drawingPwd[j + 1]].y, cyanPaint);
}
}
}
super.onDraw(canvas);
}
float moveX, moveY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawStart();
break;
case MotionEvent.ACTION_MOVE:
moveX = event.getX();
moveY = event.getY();
dealPosition(moveX, moveY);
break;
case MotionEvent.ACTION_UP:
if (unlockListener != null) {
unlockListener.drawOver(drawingPwd);
if (verificationPwd(rightPwd)) {
unlockListener.isPwdRight(true);
} else {
unlockListener.isPwdRight(false);
}
}
drawOver();
break;
}
return true;
}
private void dealPosition(float nowX, float nowY) {
nowTouchedPosition.x = nowX;
nowTouchedPosition.y = nowY;
int nowTouched = getWhichOneBeTouched(nowX, nowY);
if (nowTouched != -1) {//触摸到了点上
if (!locations[nowTouched].deawed) {//如果这点没被触摸过
drawingPwd[drawedNumber] = nowTouched; //记录密码
drawedNumber++; //被触摸点数+1
Log.v(TAG, "nowTouched " + nowTouched + " ,drawedNumber = " + drawedNumber);
}
locations[nowTouched].deawed = true;
}
invalidate();
}
private class Location {
public float x = -1, y = -1;
public boolean deawed;//是否被画过了
}
/**
* 获取被触摸到的点
*
* @param x 坐标x点
* @param y 坐标y点
* @return 被触摸的坐标点位置 或者-1
*/
private int getWhichOneBeTouched(float x, float y) {
for (int i = 0; i < locations.length; i++) {
double lPowX = Math.pow(Math.abs(x - locations[i].x), 2);
double lPowY = Math.pow(Math.abs(y - locations[i].y), 2);
if (Math.sqrt(lPowX + lPowY) < radius)
return i;
}
return -1;
}
/**
* 校验密码是否正确
*
* @param rightPwd 正确的密码
* @return 正确返回true 否则返回false
*/
public boolean verificationPwd(int[] rightPwd) {
if (rightPwd == null)
return false;
for (int i = 0; i < rightPwd.length; i++) {
if (rightPwd[i] != drawingPwd[i])
return false;
}
return true;
}
/**
* 获取当前绘制的密码
*
* @return
*/
public int[] getDrawedPwd() {
return drawingPwd;
}
/**
* 设置正确的密码
*
* @param rightPwd
*/
public void setRightPwd(int[] rightPwd) {
this.rightPwd = rightPwd;
}
//监听接口
public interface UnlockListener {
public void drawOver(int[] pwd);
public void isPwdRight(boolean isRight);
}
}
布局(view的宽高你可以随意指定,
bg1是效果里那张背景图片):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg1">
<com.cc.library.view.UnlockView
android:id="@+id/unlock_view"
android:layout_width="match_parent"
android:layout_height="360dp"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
使用:
package com.sz.china.testmoudule;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
import com.cc.library.view.UnlockView;
/**
* Created by zhangyu on 2016-07-15 14:55.
*/
public class TestUnlockViewActivity extends Activity {
private UnlockView unlockView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.unlock_activity);
unlockView = (UnlockView) findViewById(R.id.unlock_view);
//设置回调监听
unlockView.setUnlockListener(new UnlockView.UnlockListener() {
@Override
public void drawOver(int[] pwd) {//绘制完成,获取绘制的密码
unlockView.getDrawedPwd();
}
@Override
public void isPwdRight(boolean isRight) {//密码是否校验正确
if(isRight)
Toast.makeText(TestUnlockViewActivity.this,"密码正确",Toast.LENGTH_SHORT).show();
else
Toast.makeText(TestUnlockViewActivity.this,"密码错误",Toast.LENGTH_SHORT).show();
}
});
int[] pwd = {0,5,7,6};
unlockView.setRightPwd(pwd); //设置密码
}
}
资源下载地址: http://download.youkuaiyun.com/detail/chen_zhang_yu/9577759