【Android开发】自定义手势解锁组件

最近做了一个安卓APP,用到手势解锁,整理一下,分享给大家,要用到的时候,像普通的View一样直接拿来用就可以了,能省下不少功夫。也可以进行扩展,比如添加背景图片、添加数字密码备忘…

效果图如下:

1-576x1024

2-576x1024

3-575x1024

主要思路是继承View,响应并处理用户触摸事件,记录手势路径并对记录状态进行重绘。具体实现请看代码注释~
组件代码:文件名为GestureUnlockView.java

package com.scnu.gestureunlockview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by TENHAN on 2016/3/10.
 */
public class GestureUnlockView extends View {

    private static final int GESTURE_WAITING=0;  //等待输入状态
    private static final int GESTURE_DRAWING=1;  //正在输入手势状态
    private static final int GESTURE_FINISHED=2; //输入完成状态
    private static final int GESTURE_INVALID=3; //无效状态
    /**
     * 手势解锁生命周期状态
     */
    private int gestureState=GESTURE_WAITING;
    /**
     * 不同状态下的色值
     */
    private int COLOR_OUT_CIRCLE_NORMAL = Color.rgb(120, 120, 120);
    private int COLOR_OUT_CIRCLE_ONTOUCH = Color.rgb(000, 220, 220);
    private int COLOR_LINE_NORMAL = Color.argb(130, 000, 220, 255);
    private int COLOR_LINE_ERROR = Color.argb(130, 255, 000, 000);
    private int COLOR_LINE_OK = Color.argb(130, 000, 255, 000);

    /**
     * 不同状态的画笔
     */
    private Paint paintOutCircleNormal;
    private Paint paintOutCircleOnTouch;
    private Paint paintInnerCircle;
    private Paint paintLines;
    /**
     * 固定的手势触点
     */
    private Circle[] fixedCircles;
    /**
     * 保存手势路线
     */
    private Path linePath=null;
    /**
     * 已经记录的手势路径
     */
    private List<Circle> routeCircles=null;
    /**
     * 手势记录完成监听器
     */
    private OnGestureFinishedListener onGestureFinishedListener=null;
    /**
     * 记录的手势图案结果
     */
    private String gestureResult="";
    /**
     * 当前触摸事件坐标
     */
    private int eventX, eventY;
    /**
     * 触摸圆圈的内外半径
     */
    private float innerRadius,circleRadius;

    public GestureUnlockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    public GestureUnlockView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize();
    }
    public GestureUnlockView(Context context) {
        super(context);
        initialize();
    }
    /**
     * 初始化
     */
    private void initialize()
    {
        linePath = new Path();
        routeCircles= new ArrayList<Circle>();

        paintOutCircleNormal = new Paint();
        paintOutCircleNormal.setAntiAlias(true);
        paintOutCircleNormal.setStrokeWidth(3);
        paintOutCircleNormal.setStyle(Paint.Style.STROKE);

        paintOutCircleOnTouch = new Paint();
        paintOutCircleOnTouch.setAntiAlias(true);
        paintOutCircleOnTouch.setStrokeWidth(3);
        paintOutCircleOnTouch.setStyle(Paint.Style.STROKE);

        paintInnerCircle= new Paint();
        paintInnerCircle.setAntiAlias(true);
        paintInnerCircle.setStyle(Paint.Style.FILL);

        paintLines = new Paint();
        paintLines.setAntiAlias(true);
        paintLines.setStyle(Paint.Style.STROKE);
        paintLines.setStrokeWidth(6);
    }
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //计算九个触摸点的间隔距离和半径大小
        int interval=Math.min(getWidth()/6,getHeight()/6);
        if(interval>0)
        {
            circleRadius=interval*0.5f;
            innerRadius=interval*0.1f;
            if(fixedCircles == null){
                fixedCircles = new Circle[9];
            }
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    Circle circle = new Circle();
                    circle.setId(i * 3 + j+1);
                    circle.setPosX(interval * (j * 2 + 1));
                    circle.setPosY(interval * (i * 2 + 1));
                    fixedCircles[i * 3 + j] = circle;
                }
            }
        }else {
            gestureState=GESTURE_INVALID;
        }

    }

    /**
     * 绘制当前手势图案
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(gestureState==GESTURE_INVALID)return;
        for (int i = 0; i < fixedCircles.length; i++) {

            //画触摸点外圆
            if(fixedCircles[i].isUsed())
            {
                canvas.drawCircle(fixedCircles[i].getPosX(), fixedCircles[i].getPosY(),
                        circleRadius, paintOutCircleOnTouch);
            }
            else{
                canvas.drawCircle(fixedCircles[i].getPosX(), fixedCircles[i].getPosY(),
                        circleRadius, paintOutCircleNormal);
            }

        }
        //画触摸点内圆
        for (Circle circle : routeCircles) {
            canvas.drawCircle(circle.getPosX(), circle.getPosY(),
                    innerRadius, paintInnerCircle);
        }
        //画手势路径
        linePath.reset();
        if (routeCircles.size() > 0) {
            //第一个点设置为路径的起点
            Circle circle =routeCircles.get(0);
            linePath.moveTo(circle.getPosX(), circle.getPosY());
            //连接剩余路径
            for (int i = 1; i < routeCircles.size(); i++) {
                circle=routeCircles.get(i);
                linePath.lineTo(circle.getPosX(), circle.getPosY());
            }

            //如果绘制还没停止,连接多一条线路跟随手指
            if(gestureState==GESTURE_DRAWING) {
                linePath.lineTo(eventX, eventY);
            }
            canvas.drawPath(linePath, paintLines);
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                reset();
                break;
            case MotionEvent.ACTION_MOVE:

                if(gestureState==GESTURE_FINISHED||gestureState==GESTURE_WAITING)
                {
                    startGesture();
                }
                if(gestureState==GESTURE_DRAWING)
                {
                    eventX = (int) event.getX();
                    eventY = (int) event.getY();
                    //判断是否有未选中的圆圈被选中
                    for (int i = 0; i < fixedCircles.length; i++) {
                        if (fixedCircles[i].isInTouch(eventX, eventY) && !fixedCircles[i].isUsed()) {
                            fixedCircles[i].setUsed(true);
                            routeCircles.add(fixedCircles[i]);
                            break;
                        }
                }
                }
                break;
            case MotionEvent.ACTION_UP:
                //当用户松开时,则完成手势记录
                finishedGesture();
                break;
        }
        //更新绘图
        invalidate();
        //不能返回false,避免被重绘
        return true;
    }


    /**
     * 开始记录手势
     */
    private void startGesture()
    {
        reset();
        gestureState=GESTURE_DRAWING;
    }

    /**
     * 结束记录手势
     */
    private void finishedGesture()
    {
        //当手势图不为空时,才返回结果
        if(routeCircles.size()>0&&gestureState==GESTURE_DRAWING) {
            gestureState = GESTURE_FINISHED;
            paintInnerCircle.setColor(COLOR_LINE_OK);
            paintLines.setColor(COLOR_LINE_OK);
            for (Circle circle : routeCircles) {
                gestureResult += circle.getId();
            }
            if (onGestureFinishedListener != null)
                onGestureFinishedListener.onGestureFinished(gestureResult);
        }
    }
    /**
     * 设置监听结果接口
     * @param onGestureFinishedListener
     */
    public void setOnGestureFinishListener(
            OnGestureFinishedListener onGestureFinishedListener) {
        this.onGestureFinishedListener = onGestureFinishedListener;
    }
    /**
     * 重置手势解锁
     */
    public void reset()
    {
        //清除旧的记录
        for (int i = 0; i < fixedCircles.length; i++) {
            fixedCircles[i].setUsed(false);
        }
        gestureResult="";
        routeCircles.clear();
        linePath.reset();
        gestureState=GESTURE_WAITING;
        paintOutCircleNormal.setColor(COLOR_OUT_CIRCLE_NORMAL);
        paintOutCircleOnTouch.setColor(COLOR_OUT_CIRCLE_ONTOUCH);
        paintInnerCircle.setColor(COLOR_LINE_NORMAL);
        paintLines.setColor(COLOR_LINE_NORMAL);
        invalidate();
    }

    /**
     * 验证成功,通知组件改变手势图案颜色
     */
    public void verifySucceed()
    {
        paintLines.setColor(COLOR_LINE_OK);
        invalidate();
    }
    /**
     * 验证失败,通知组件改变手势图案颜色
     */
    public void verifyFail()
    {
        paintLines.setColor(COLOR_LINE_ERROR);
        invalidate();
    }

    /**
     * 用于连接手势图案的圆圈类
     */
    private class Circle{
        private int posX;
        private int posY;
        private int id;
        private boolean isUsed;
        public int getPosX()
        {
            return posX;
        }
        public int getPosY()
        {
            return posY;
        }
        public int getId(){
            return id;
        }
        public void setPosX(int x)
        {
            posX=x;
        }
        public void setPosY(int y)
        {
            posY=y;
        }
        public void setId(int id)
        {
            this.id=id;
        }
        public void setUsed(boolean used)
        {
            isUsed=used;
        }
        public boolean isUsed()
        {
            return isUsed;
        }
        //手指是否移动进入触摸点
        public boolean isInTouch(int x,int y)
        {
            return Math.sqrt((x - posX) * (x - posX) + (y - posY) * (y - posY))<circleRadius;
        }
     }
}

接口文件名:OnGestureFinishedListener.java

package com.scnu.gestureunlockview;

/**
 * Created by TENHAN on 2016/3/10.
 */
public interface OnGestureFinishedListener {
    public void onGestureFinished(String result);
}

布局代码:文件名为:activity_main.xml

<?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.scnu.gestureunlockview.MainActivity">

    <TextView
        android:id="@+id/tv_gesture_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="手势结果为:"
        android:layout_margin="10dp"
        android:textSize="20sp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        />
    <com.scnu.gestureunlockview.GestureUnlockView
        android:id="@+id/guv_unlock"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/tv_gesture_result"/>
</RelativeLayout>

启动Activity的代码,文件名为MainActivity.java

package com.scnu.gestureunlockview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView tv_gesture_result=null;
    private GestureUnlockView gestureUnlockView=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_gesture_result=(TextView)this.findViewById(R.id.tv_gesture_result);
        gestureUnlockView=(GestureUnlockView)this.findViewById(R.id.guv_unlock);
        gestureUnlockView.setOnGestureFinishListener(new OnGestureFinishedListener() {
            @Override
            public void onGestureFinished(String result) {
                tv_gesture_result.setText("返回的结果为:"+result);
            }
        });

    }
}

完。

发表评论

电子邮件地址不会被公开。 必填项已用*标注