Android 自定义View 当然是十分重要的,笔者这两天写了一个自定义 View 的手势密码,和大家分享分享:
首先,我们来创建一个表示点的类,Point.java:
public class Point {
// 点的三种状态
public static final int POINT_STATUS_NORMAL = 0;
public static final int POINT_STATUS_CLICK = 1;
public static final int POINT_STATUS_ERROR = 2;
// 默认状态
public int state = POINT_STATUS_NORMAL;
// 点的坐标
public float mX;
public float mY;
public Point(float x,float y){
this.mX = x;
this.mY = y;
}
// 获取两个点的距离
public float getInstance(Point a){
return (float) Math.sqrt((mX-a.mX)*(mX-a.mX)+(mY-a.mY)*(mY-a.mY));
}
}
然后我们创建一个 HandleLock.java 继承自 View,并重写其三种构造方法(不重写带两个参数的构造方法会导致程序出错):
首先,我们先把后面需要用的变量写出来,方便大家明白这些变量是干嘛的:
// 三种画笔
private Paint mNormalPaint;
private Paint mClickPaint;
private Paint mErrorPaint;
// 点的半径
private float mRadius;
// 九个点,使用二维数组
private Point[][] mPoints = new Point[3][3];
// 保存手势划过的点
private ArrayList<Point> mClickPointsList = new ArrayList<Point>();
// 手势的 x 坐标,y 坐标
private float mHandleX;
private float mHandleY;
private OnDrawFinishListener mListener;
// 保存滑动路径
private StringBuilder mRoute = new StringBuilder();
// 是否在画错误状态
private boolean isDrawError = false;
接下来我们来初始化数据:
// 初始化数据
private void initData() {
// 初始化三种画笔,正常状态为灰色,点下状态为蓝色,错误为红色
mNormalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mNormalPaint.setColor(Color.parseColor("#ABABAB"));
mClickPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mClickPaint.setColor(Color.parseColor("#1296db"));
mErrorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mErrorPaint.setColor(Color.parseColor("#FB0C13"));
// 获取点间隔
float offset = 0;
if (getWidth() > getHeight()) {
// 横屏
offset = getHeight() / 7;
mRadius = offset / 2;
mPoints[0][0] = new Point(getWidth() / 2 - offset * 2, offset + mRadius);
mPoints[0][1] = new Point(getWidth() / 2, offset + mRadius);
mPoints[0][2] = new Point(getWidth() / 2 + offset * 2, offset + mRadius);
mPoints[1][0] = new Point(getWidth() / 2 - offset * 2, offset * 3 + mRadius);
mPoints[1][1] = new Point(getWidth() / 2, offset * 3 + mRadius);
mPoints[1][2] = new Point(getWidth() / 2 + offset * 2, offset * 3 + mRadius);
mPoints[2][0] = new Point(getWidth() / 2 - offset * 2, offset * 5 + mRadius);
mPoints[2][1] = new Point(getWidth() / 2, offset * 5 + mRadius);
mPoints[2][2] = new Point(getWidth() / 2 + offset * 2, offset * 5 + mRadius);
} else {
// 竖屏
offset = getWidth() / 7;
mRadius = offset / 2;
mPoints[0][0] = new Point(offset + mRadius, getHeight() / 2 - 2 * offset);
mPoints[0][1] = new Point(offset * 3 + mRadius, getHeight() / 2 - 2 * offset);
mPoints[0][2] = new Point(offset * 5 + mRadius, getHeight() / 2 - 2 * offset);
mPoints[1][0] = new Point(offset + mRadius, getHeight() / 2);
mPoints[1][1] = new Point(offset * 3 + mRadius, getHeight() / 2);
mPoints[1][2] = new Point(offset * 5 + mRadius, getHeight() / 2);
mPoints[2][0] = new Point(offset + mRadius, getHeight() / 2 + 2 * offset);
mPoints[2][1] = new Point(offset * 3 + mRadius, getHeight() / 2 + 2 * offset);
mPoints[2][2] = new Point(offset * 5 + mRadius, getHeight() / 2 + 2 * offset);
}
}
大家可以看到,我来给点定坐标是,是按照比较窄的边的 1/7 作为点的直径,这样保证了,不管你怎么定义 handleLock 的宽高,都可以使里面的九个点看起来位置很舒服。
接下来我们就需要写一些函数,将点、线绘制到控件上,我自己把绘制分成了三部分,一部分是点,一部分是点与点之间的线,一部分是手势的小点和手势到最新点的线。
// 画点,按照我们选择的半径画九个圆
private void drawPoints(Canvas canvas) {
// 便利所有的点,并且判断这些点的状态
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
Point point = mPoints[i][j];
switch (point.state) {
case Point.POINT_STATUS_NORMAL:
canvas.drawCircle(point.mX, point.mY, mRadius, mNormalPaint);
break;
case Point.POINT_STATUS_CLICK:
canvas.drawCircle(point.mX, point.mY, mRadius, mClickPaint);
break;
case Point.POINT_STATUS_ERROR:
canvas.drawCircle(point.mX, point.mY, mRadius, mErrorPaint);
break;
default:
break;
}
}
}
}
// 画点与点之间的线
private void drawLines(Canvas canvas) {
// 判断手势是否已经划过点了
if (mClickPointsList.size() > 0) {
Point prePoint = mClickPointsList.get(0);
// 将所有已选择点的按顺序连线
for (int i = 1; i < mClickPointsList.size(); i++) {
// 判断已选择点的状态
if (prePoint.state == Point.POINT_STATUS_CLICK) {
mClickPaint.setStrokeWidth(7);
canvas.drawLine(prePoint.mX, prePoint.mY, mClickPointsList.get(i).mX, mClickPointsList.get(i).mY, mClickPaint);
}
if (prePoint.state == Point.POINT_STATUS_ERROR) {
mErrorPaint.setStrokeWidth(7);
canvas.drawLine(prePoint.mX, prePoint.mY, mClickPointsList.get(i).mX, mClickPointsList.get(i).mY, mErrorPaint);
}
prePoint = mClickPointsList.get(i);
}
}
}
// 画手势点
private void drawFinger(Canvas canvas) {
// 有选择点后再出现手势点
if (mClickPointsList.size() > 0) {
canvas.drawCircle(mHandleX, mHandleY, mRadius / 2, mClickPaint);
}
// 最新点到手指的连线,判断是否有已选择的点,有才能画
if (mClickPointsList.size() > 0) {
canvas.drawLine(mClickPointsList.get(mClickPointsList.size() - 1).mX, mClickPointsList.get(mClickPointsList.size() - 1).mY,
mHandleX, mHandleY, mClickPaint);
}
}
上面的代码我们看到需要使用到手势划过的点,我们是怎么选择的呢?
// 获取手指移动中选取的点
private int[] getPositions() {
Point point = new Point(mHandleX, mHandleY);
int[] position = new int[2];
// 遍历九个点,看手势的坐标是否在九个圆内,有则返回这个点的两个下标
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (mPoints[i][j].getInstance(point) <= mRadius) {
position[0] = i;
position[1] = j;
return position;
}
}
}
return null;
}
我们需要重写其 onTouchEvent 来通过手势动作来提交选择的点,并更新视图:
// 重写点击事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取手势的坐标
mHandleX = event.getX();
mHandleY = event.getY();
int[] position;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
position = getPositions();
// 判断点下时是否选择到点
if (position != null) {
// 添加到已选择点中,并改变其状态
mClickPointsList.add(mPoints[position[0]][position[1]]);
mPoints[position[0]][position[1]].state = Point.POINT_STATUS_CLICK;
// 保存路径,依次保存其横纵下标
mRoute.append(position[0]);
mRoute.append(position[1]);
}
break;
case MotionEvent.ACTION_MOVE:
position = getPositions();
// 判断手势移动时是否选择到点
if (position != null) {
// 判断当前选择的点是否已经被选择过
if (!mClickPointsList.contains(mPoints[position[0]][position[1]])) {
// 添加到已选择点中,并改变其状态
mClickPointsList.add(mPoints[position[0]][position[1]]);
mPoints[position[0]][position[1]].state = Point.POINT_STATUS_CLICK;
// 保存路径,依次保存其横纵下标
mRoute.append(position[0]);
mRoute.append(position[1]);
}
}
break;
case MotionEvent.ACTION_UP:
// 重置数据
resetData();
break;
default:
break;
}
// 更新视图
invalidate();
return true;
}
// 重置数据
private void resetData() {
// 将所有选择过的点的状态改为正常
for (Point point :
mClickPointsList) {
point.state = Point.POINT_STATUS_NORMAL;
}
// 清空已选择点
mClickPointsList.clear();
// 清空保存的路径
mRoute = new StringBuilder();
// 不再画错误状态
isDrawError = false;
}
那我们怎么绘制视图呢?我们通过重写其 onDraw() 方法:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 判断是否画错误状态,画错误状态不需要画手势点已经于最新选择点的连线
if (isDrawError) {
drawPoints(canvas);
drawLines(canvas);
} else {
drawPoints(canvas);
drawLines(canvas);
drawFinger(canvas);
}
}
那么这个手势密码绘制过程就结束了,但是整个控件还没有结束,我们还需要给它一个监听器,监听其绘制完成,选择后续事件:
private OnDrawFinishListener mListener;
// 定义绘制完成的接口
public interface OnDrawFinishListener {
public boolean drawFinish(String route);
}
// 定义绘制完成的方法,传入接口
public void setOnDrawFinishListener(OnDrawFinishListener listener) {
this.mListener = listener;
}
然后我们就需要在手势离开的时候 ,来进行绘制完成时的事件:
case MotionEvent.ACTION_UP:
// 完成时回调绘制完成的方法,返回比对结果,判断手势密码是否正确
mListener.drawFinish(mRoute.toString());
// 返回错误,则将所有已选择点状态改为错误
if (!mListener.drawFinish(mRoute.toString())) {
for (Point point :
mClickPointsList) {
point.state = Point.POINT_STATUS_ERROR;
}
// 将是否绘制错误设为 true
isDrawError = true;
// 刷新视图
invalidate();
// 这里我们使用 handler 异步操作,使其错误状态保持 0.5s
new Thread(new Runnable() {
@Override
public void run() {
if (!mListener.drawFinish(mRoute.toString())) {
Message message = new Message();
message.arg1 = 0;
handler.sendMessage(message);
}
}
}).run();
} else {
resetData();
}
invalidate();
break;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.arg1) {
case 0:
try {
// 沉睡 0.5s
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 重置数据,并刷新视图
resetData();
invalidate();
break;
default:
break;
}
}
};
好了,handleLock,整个过程就结束了,笔者这里定义了一个监听器只是给大家提供一种思路,笔者将保存的大路径传给了使用者,是为了保证使用者可以自己保存密码,并作相关操作,大家也可以使用 HandleLock 来 保存密码,不传给使用者,根据自己的需求写出更多更丰富的监听器,而且这里笔者在 MotionEvent.ACTION_UP 中直接回调了 drawFinish() 方法,就意味着要使用该 HandleLock 就必须给它设置监听器。
接下来我们说说 HandleLock 的使用,首先是在布局文件中使用:
<com.example.a01378359.testapp.lock.HandleLock
android:id="@+id/handlelock_test"
android:layout_width="match_parent"
android:layout_height="match_parent" />
接下来是代码中使用:
handleLock = findViewById(R.id.handlelock_test);
handleLock.setOnDrawFinishListener(new HandleLock.OnDrawFinishListener() {
@Override
public boolean drawFinish(String route) {
// 第一次滑动,则保存密码
if (count == 0){
password = route;
count++;
Toast.makeText(LockTestActivity.this,"已保存密码",Toast.LENGTH_SHORT).show();
return true;
}else {
// 与保存密码比较,返回结果,并且做出相应事件
if (password.equals(route)){
Toast.makeText(LockTestActivity.this,"密码正确",Toast.LENGTH_SHORT).show();
return true;
}else {
Toast.makeText(LockTestActivity.this,"密码错误",Toast.LENGTH_SHORT).show();
return false;
}
}
}
});
项目地址:源代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持亿速云。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。