1.先看效果:
2.分析
2.1 建立三个画笔,一个用于绘制外圆,一个绘制内圆,一个是绘制直线
private void initPaint() {
mLinePaint = new Paint();
mLinePaint.setAntiAlias(true);
mLinePaint.setColor(mPressedColor);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(6);
mOuterPaint = new Paint();
mOuterPaint.setAntiAlias(true);
mOuterPaint.setColor(mNormalColor);
mOuterPaint.setStyle(Paint.Style.STROKE);
mOuterPaint.setStrokeWidth(6);
mInnerPaint = new Paint();
mInnerPaint.setAntiAlias(true);
mInnerPaint.setColor(mNormalColor);
mInnerPaint.setStyle(Paint.Style.FILL);
}
2.2 设定圆圈座标
一定要熟悉三角形的勾股定理算法,#勾股定理 Math.abs()取绝对值 Math.pow(a,b)取a的b次方 Math.sqrt()开平方。
private void initPoint() {
mPointList.clear();
int width = getWidth();
int height = getHeight();
float offsetY = 0.0f;
float offsetX = 0.0f;
float circleWidth;
//兼容横竖屏
if(width > height){
offsetX = ( width - height ) / 2;
circleWidth = height / 3 ;
}else {
offsetY = ( height - width ) / 2;
circleWidth = width / 3 ;
}
mPointList.add(new Point(offsetX + circleWidth / 2 , offsetY + circleWidth / 2 ,mLockNormalValue[0]));
mPointList.add(new Point(offsetX + 3 * circleWidth / 2 , offsetY + circleWidth / 2 ,mLockNormalValue[1]));
mPointList.add(new Point(offsetX + 5 * circleWidth / 2 , offsetY + circleWidth / 2 ,mLockNormalValue[2]));
mPointList.add(new Point(offsetX + circleWidth / 2 , offsetY + 3 * circleWidth / 2 ,mLockNormalValue[3]));
mPointList.add(new Point(offsetX + 3 * circleWidth / 2 , offsetY + 3 * circleWidth / 2 ,mLockNormalValue[4]));
mPointList.add(new Point(offsetX + 5 * circleWidth / 2 , offsetY + 3 * circleWidth / 2 ,mLockNormalValue[5]));
mPointList.add(new Point(offsetX + circleWidth / 2 , offsetY + 5 * circleWidth / 2 ,mLockNormalValue[6]));
mPointList.add(new Point(offsetX + 3 * circleWidth / 2 , offsetY + 5 * circleWidth / 2 ,mLockNormalValue[7]));
mPointList.add(new Point(offsetX + 5 * circleWidth / 2 , offsetY + 5 * circleWidth / 2 ,mLockNormalValue[8]));
}
3. onDraw() 绘制画圆圈
for(Point point : mPointList){
//根据不同的状态 设置不同的画笔颜色
if(point.status == STATUS_PRESSED){
mOuterPaint.setColor(mPressedColor);
mInnerPaint.setColor(mPressedColor);
}else if(point.status == STATUS_DEFAULT){
mOuterPaint.setColor(mNormalColor);
mInnerPaint.setColor(mNormalColor);
}else if(point.status == STATUS_ERROR){
mOuterPaint.setColor(mErrorColor);
mInnerPaint.setColor(mErrorColor);
}else if(point.status == STATUS_CORRECT){
mOuterPaint.setColor(mCorrectColor);
mInnerPaint.setColor(mCorrectColor);
}
//画外圆
canvas.drawCircle(point.centerX, point.centerY, mRadius, mOuterPaint);
//画内圆
canvas.drawCircle(point.centerX, point.centerY, mRadius / 6, mInnerPaint);
}
}
4. onDraw() 绘制直线
注意:mMoveX 和 mMoveY 在手势抬起的时候,要判断一下是否等于 0 ,防止绘制多余的线。在onTouchEvent中的UP事件中把这两个值置为0
//画线
if(mPressedPointList.size() > 0){
Point point = mPressedPointList.get(mPressedPointList.size() - 1);
//抬起的时候不需要再画多出来的直线
if( mMoveX != 0.0f && mMoveY != 0.0f) {
canvas.drawLine(point.centerX, point.centerY,
mMoveX, mMoveY, mLinePaint);
}
for(int i = 0 ; i < mPressedPointList.size()-1 ; i ++ ){
canvas.drawLine(mPressedPointList.get(i).centerX,mPressedPointList.get(i).centerY,
mPressedPointList.get(i+1).centerX,mPressedPointList.get(i+1).centerY,mLinePaint);
}
}
5. onTouchEvent() 手势监听
每个需求的逻辑可能不一样,可自行修改,我这里是如果连接的两个点中包含某一个点,会把这个点连接上。两个座标点的中点点座标为 两端座标的座标之和的一半。判断是否按下点是根据 按下点的做和座标点之间的距离是否小于半径,小于则是按下某一个点,或是触摸到了某一个点。
@Override
public boolean onTouchEvent(MotionEvent event) {
Point point = null;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mMoveX = event.getX();
mMoveY = event.getY();
//按下的点和圆心的距离是否大于半径
point = judgeInCircle(mMoveX, mMoveY);
if(point!=null){
mPressedPointList.add(point);
}
break;
case MotionEvent.ACTION_MOVE:
mMoveX = event.getX();
mMoveY = event.getY();
//按下的点和圆心的距离是否大于半径
point = judgeInCircle(mMoveX,mMoveY);
if(point!=null) {
Point lastPoint = mPressedPointList.get(mPressedPointList.size() - 1);
float centerX1 = Math.abs(lastPoint.centerX + point.centerX) / 2;
float centerY1 = Math.abs(lastPoint.centerY + point.centerY) / 2;
Point centerPoint = judgeInCircle(centerX1, centerY1);
if(centerPoint!=null) {
mPressedPointList.add(centerPoint);
}
mPressedPointList.add(point);
}
break;
case MotionEvent.ACTION_UP:
mMoveX = 0.0f;
mMoveY = 0.0f;
showResult();
break;
}
invalidate();
return true;
}
/**
* 恢复数据
*/
private void recoverStatus(){
mPressedPointList.clear();
for(Point point : mPointList){
point.status = STATUS_DEFAULT;
}
mLinePaint.setColor(mPressedColor);
invalidate();
}
/**
* 判断划过的点是否是圆点
*/
private Point judgeInCircle(float centerX,float centerY){
for(Point point : mPointList){
//勾股定理 Math.abs()取绝对值 Math.pow(a,b)取a的b次方 Math.sqrt()开平方
double x2 = Math.pow(Math.abs(centerX - point.centerX), 2);
double y2 = Math.pow(Math.abs(centerY - point.centerY), 2);
double r = Math.sqrt( x2 + y2);
if(r < mRadius){
if(!mPressedPointList.contains(point)) {
point.status = STATUS_PRESSED;
return point;
}
}
}
return null;
}
6. 结果比较和回调
/**
* 显示结果
*/
private void showResult(){
int status;
String result = "";
for(Point point : mPressedPointList){
result += point.psw;
}
if(result.equals(mNormalPassword)){
status = 1;
mLinePaint.setColor(mCorrectColor);
}else {
status = 2;
mLinePaint.setColor(mErrorColor);
}
if(this.lockValueCallBack!=null){
this.lockValueCallBack.valueCallBack(result);
}
for(Point point : mPointList){
if(point.status == STATUS_PRESSED) {
if (status == 1) {
point.status = STATUS_CORRECT;
} else if (status == 2) {
point.status = STATUS_ERROR;
}
}
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
recoverStatus();
}
},500);
}
/**
* 设置正确密码
* @param correctValue
*/
public void setCorrectValue(String correctValue){
this.mNormalPassword = correctValue;
}
private LockValueCallBack lockValueCallBack;
protected void setLockValueCallBack(LockValueCallBack lockValueCallBack){
this.lockValueCallBack = lockValueCallBack;
}
protected interface LockValueCallBack{
void valueCallBack(String value);
}
static class Point{
private float centerX;
private float centerY;
private String psw;
private int status;
public Point(float centerX, float centerY, String psw){
this.centerX = centerX;
this.centerY = centerY;
this.psw = psw;
this.status = STATUS_DEFAULT;
}
public String toString(){
return "centerX="+centerX+" centerY="+centerY + " psw="+psw +" status="+ this.status;
}
}
7. 自定义属性
public GestureLockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.GestureLockView);
mNormalColor = typedArray.getColor(R.styleable.GestureLockView_defaultColor,mNormalColor);
mPressedColor = typedArray.getColor(R.styleable.GestureLockView_pressedColor,mPressedColor);
mErrorColor = typedArray.getColor(R.styleable.GestureLockView_errorColor,mErrorColor);
mCorrectColor = typedArray.getColor(R.styleable.GestureLockView_correctColor,mCorrectColor);
mRadius = (int) typedArray.getDimension(R.styleable.GestureLockView_radius,mRadius);
typedArray.recycle();
}
8. attrs.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="GestureLockView">
<attr name="pressedColor" format="color"/>
<attr name="defaultColor" format="color"/>
<attr name="errorColor" format="color"/>
<attr name="correctColor" format="color"/>
<attr name="radius" format="dimension"/>
</declare-styleable>
</resources>
9. 源码地址
https://github.com/panshimu/GestureLockView
有问题随时留言,看到会马上回复的,欢迎讨论!!!
QQ 362976241