我个人对Android原生系统是比较钟爱的。Android原生手势锁只有点和线构成,也将扁平化做到了极致。今天我就来通过自定义控件的形式纯手工打造一个高仿的原生手势锁控件。
android 原生手势锁效果图及描述
原生效果图 :
原生解锁的整体预览效果
一些细节展示
我们首先来描述一下我们要实现的内容。有时候程序员如果先把问题描述清楚,解决问题的时候也会事半功倍。
先说静止的,就是9个白点组成的3X3矩阵,相邻白点之间的间距相同。
变化的内容,变化是由于手指的触摸事件引起的。每个点在其周围都有一个‘领域’范围,如果手指移动到了这个领域内就会触发该点‘被选中’的事件,效果是引起该点的一个放大然后缩小的动画,并且该点也被连入一个特定的路径中,如果路径中穿过了一个未被连接入路径的点,那么这个点会被自动接入到该路径。手指从上一个接入手势路径的点离开的时候有一个从该点到手指触点的一个线段,观察得知,这个多出来的线段在一定范围s内是不显示的,当线段长度从s到2s变化的时候线段的透明度也会从0变为1。手指抬起,如果密码正确手势锁消失,否则改变错误的路径颜色。
先看下我们自定义控件实现的效果。
整体效果
控件细节
Android原生手势锁逻辑分析
根据描述我们需要在view的onDraw方法中画的东西分两部分:
不变的部分,9个点,可以用一个数组表示。
变化的部分,即手指移动产生的手势路径,这个路径包含三部分:路径经过的点,点与点之间连线的路径,还有手指移动时候多出来的一段路径。所以变化的部分我们封装入一个类PWDPath 中。
其中相应触摸操作的是第二部分,只需要监听onTouchEvent事件然后改变PWDPath 对象的内容,然后调用invalidate()即可。
绘制内容的尺寸显然跟控件大小有关,我们把这些尺寸的确定放在控件尺寸确定之后。为了方便我们把view的形状设置成正方形。然后根据边长设置内容部分的尺寸(点半径、点间距等)。
密码的验证:我们预先指定点所对应 的数字如下,根据密码的连线的到对应的数字序列字符串,与我们保存的密码比较即可。
代码部分
由于代码中注释很清楚,就不在详细说明了,自定义两个属性,正常颜色NORMAL_COLOR和错误颜色ERROR_COLOR方便定制。
package com.sovnem.originallock;
//省去一亩炮特部分
/**
* 视图结构为3X3的矩阵点集合,点周围一定区域为有效范围s,这个有效范围不能大于相邻两个点的距离的一半。
* 手指落在这个有效范围s内,则判定该touch对这个点有效,手势路径就会将这个点计算在内 手指落在点内则会有一个该点逐渐放大然后缩小的动画效果
* 手指从一个点移出的时候在有效范围s内
* ,手指到该点之间的线段是不显示的,当手指移出范围s,到手指离该点2s之间过程手指与该点之间的线段逐渐显示,所以综上来说s是小于点点间距的三分之一的
* 如果手势密码错误则已经连接的线和点都变成橘黄色,并持续三秒钟再复原,如果在这三秒钟内有手势操作则立即复原
*
*
* @author monkey-d-wood
*/
public class GestureLockView extends View {
private static final int ANIM_DURATION = 150;// 点选中时候的动画时间
private static final int RECOVER_DURATION = 3000;// 错误路径持续时长
private int NORMAL_COLOR;// 正常的颜色
private int ERROR_COLOR;// 手势错误颜色
private final int row = 3;// 矩阵的边数
private PWDPath ppath;// 密码路径
private PPoint[][] points = new PPoint[3][3];
private boolean isRight = true;
public GestureLockView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
public GestureLockView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public GestureLockView(Context context) {
super(context);
init(null);
}
private void init(AttributeSet attrs) {
// NORMAL_COLOR = Color.WHITE;
// ERROR_COLOR = Color.parseColor("#F54F20");
if (attrs != null) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.lockview);
NORMAL_COLOR = ta.getColor(R.styleable.lockview_normalColor, Color.WHITE);
ERROR_COLOR = ta.getColor(R.styleable.lockview_wrongColor, Color.parseColor("#F54F20"));
ta.recycle();
}
ppath = new PWDPath();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawPoints(canvas);
ppath.draw(canvas, isRight);
}
private void drawPoints(Canvas canvas) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < row; j++) {
points[i][j].draw(canvas, true);
}
}
}