Android-手势密码
效果图
手势密码绘制界面(绘制上方的9个提示图标和9个宫格密码图标)设置手势密码(监听手势的输入,TouchEvent的事件处理,获取输入的手势密码,同时显示在上方的提示区域)
再绘制一次,两次密码不一致提示界面(在实现的时候,错误提示文字增加了“左右晃动的动画”,错误路径颜色标记为红色)
实现思路
1. 正上方的提示区域,用一个类(LockIndicator.java)来实现,自定义view来绘制9个提示图标;
2. 手势密码绘制区域,用一个类(GestureContentView.java)来实现,它继承ViewGroup,添加9个ImageView来表示图标,在onLayout()方法中设置它们的位置;
3. 手势路径绘制,用一个类(GestureDrawline.java)来实现,复写onTouchEvent()方法,在这个方法里监听TouchEvent事件:ACTION_DOWN、ACTION_MOVE、ACTION_UP事件,来绘制手势连接不同点之间的路径;
4. 9个点的对象,用一个类(GesturePoint.java)来实现,保存它的位置、状态、背景图片等相关信息;
5. 手势密码的获取,判断手指当前的位置,根据滑动路径经过的点,按顺序保存绘制的点的顺序(这里点的顺序从上到下分别是1,2,3,4,5,6,7,8,9),不能有重复的点。
诶嘿,故事的发展是这样的~
手势设置Activity(GestureEditActivity)
/**
*
* 手势密码设置界面
*
*/
public class GestureEditActivity extends SlidingActivity implements View.OnClickListener {
private TextView mTextTitle;
private TextView mTextCancel;
private LockIndicator mLockIndicator;
private TextView mTextTip;
private FrameLayout mGestureContainer;
private GestureContentView mGestureContentView;
private TextView mTextReset;
private boolean mIsFirstInput = true;
private String mFirstPassword = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gesture_edit);
setUpViews();
setUpListeners();
}
private void setUpViews() {
mTextTitle = (TextView) findViewById(R.id.text_title);
mTextCancel = (TextView) findViewById(R.id.text_cancel);
mTextReset = (TextView) findViewById(R.id.text_reset);
mTextReset.setClickable(false);
//这个地方就是上面那个框框的自定义
mLockIndicator = (LockIndicator) findViewById(R.id.lock_indicator);
mTextTip = (TextView) findViewById(R.id.text_tip);
mGestureContainer = (FrameLayout) findViewById(R.id.gesture_container);
// 初始化一个显示各个点的viewGroup,这个地方就是手势密码容器类,它继承自viewGroup,并包含9个imageview
mGestureContentView = new GestureContentView(this, false, "", new GestureDrawline.GestureCallBack() {
@Override
public void onGestureCodeInput(String inputCode) {
if (!isInputPassValidate(inputCode)) {
mTextTip.setText(Html.fromHtml("<font color='#c70c1e'>最少链接4个点, 请重新输入</font>"));
mGestureContentView.clearDrawlineState(0L);
return;
}
if (mIsFirstInput) {
mFirstPassword = inputCode;
updateCodeList(inputCode);
mGestureContentView.clearDrawlineState(0L);
mTextReset.setClickable(true);
mTextReset.setText(getString(R.string.reset_gesture_code));
} else {
if (inputCode.equals(mFirstPassword)) {
Toast.makeText(GestureEditActivity.this, "设置成功", Toast.LENGTH_SHORT).show();
mGestureContentView.clearDrawlineState(0L);
SlidingActivity.password = inputCode;
GestureEditActivity.this.finish();
} else {
mTextTip.setText(Html.fromHtml("<font color='#c70c1e'>与上一次绘制不一致,请重新绘制</font>"));
// 左右移动动画
Animation shakeAnimation = AnimationUtils.loadAnimation(GestureEditActivity.this, R.anim.shake);
mTextTip.startAnimation(shakeAnimation);
// 保持绘制的线,1.5秒后清除
mGestureContentView.clearDrawlineState(1300L);
}
}
mIsFirstInput = false;
}
@Override
public void checkedSuccess() {
}
@Override
public void checkedFail() {
}
});
// 设置手势解锁显示到哪个布局里面
mGestureContentView.setParentView(mGestureContainer);
updateCodeList("");
}
private void setUpListeners() {
mTextCancel.setOnClickListener(this);
mTextReset.setOnClickListener(this);
}
private void updateCodeList(String inputCode) {
// 更新选择的图案,这个地方就是将密码code更新到上面的小图标上(这个地方只是将)
Log.e("ddd",inputCode);
mLockIndicator.setPath(inputCode);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.text_cancel:
this.finish();
break;
case R.id.text_reset:
mIsFirstInput = true;
updateCodeList("");
mTextTip.setText(getString(R.string.set_gesture_pattern));
break;
default:
break;
}
}
private boolean isInputPassValidate(String inputPassword) {
if (TextUtils.isEmpty(inputPassword) || inputPassword.length() < 4) {
return false;
}
return true;
}
}
啊哈,问题是不是来了,就是GestureContentView和LockIndicator是咋画的呢,是哈,我也不知道是怎么画出来的,那么就看人家是怎么画出来的吧好伐。。。
/**
* 手势密码容器类
*
*/
public class GestureContentView extends ViewGroup {
private int baseNum = 6;
private int[] screenDispaly;
/**
* 每个点区域的宽度
*/
private int blockWidth;
/**
* 声明一个集合用来封装坐标集合
*/
//这个类里面表示各个点的属性,以及对应不同状态下点的图标
private List<GesturePoint> list;
private Context context;
private boolean isVerify;
private GestureDrawline gestureDrawline;
/**
* 包含9个ImageView的容器,初始化
* @param context
* @param isVerify 是否为校验手势密码
* @param passWord 用户传入密码
* @param callBack 手势绘制完毕的回调
*/
public GestureContentView(Context context, boolean isVerify, String passWord, GestureDrawline.GestureCallBack callBack) {
super(context);
screenDispaly = AppUtil.getScreenDispaly(context);
blockWidth = screenDispaly[0]/3;
this.list = new ArrayList<GesturePoint>();
this.context = context;
this.isVerify = isVerify;
// 添加9个图标
addChild();
// 初始化一个可以画线的view
gestureDrawline = new GestureDrawline(context, list, isVerify, passWord, callBack);
}
private void addChild(){
for (int i = 0; i < 9; i++) {
ImageView image = new ImageView(context);
image.setBackgroundResource(R.drawable.gesture_node_normal);
this.addView(image);
invalidate();
// 第几行
int row = i / 3;
// 第几列
int col = i % 3;
// 定义点的每个属性
int leftX = col*blockWidth+blockWidth/baseNum;
int topY = row*blockWidth+blockWidth/baseNum;
int rightX = col*blockWidth+blockWidth-blockWidth/baseNum;
int bottomY = row*blockWidth+blockWidth-blockWidth/baseNum;
GesturePoint p = new GesturePoint(leftX, rightX, topY, bottomY, image,i+1);
this.list.add(p);
}
}
public void setParentView(ViewGroup parent){
// 得到屏幕的宽度
int width = screenDispaly[0];
LayoutParams layoutParams = new LayoutParams(width, width);
this.setLayoutParams(layoutParams);
gestureDrawline.setLayoutParams(layoutParams);
parent.addView(gestureDrawline);
parent.addView(this);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
//第几行
int row = i/3;
//第几列
int col = i%3;
View v = getChildAt(i);
v.layout(col*blockWidth+blockWidth/baseNum, row*blockWidth+blockWidth/baseNum,
col*blockWidth+blockWidth-blockWidth/baseNum, row*blockWidth+blockWidth-blockWidth/baseNum);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 遍历设置每个子view的大小
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
v.measure(widthMeasureSpec, heightMeasureSpec);
}
}
/**
* 保留路径delayTime时间长
* @param delayTime
*/
public void clearDrawlineState(long delayTime) {
gestureDrawline.clearDrawlineState(delayTime);
}
}
这个里面就包含了各个点的属性类GesturePoint,以及划线的类GestureDrawline。GesturePoint就有点的坐标和图片样式等属性,很简单,不贴了,GestureDrawline设置了画笔的各种属性,以及不同手势下画笔的路径,同样不想贴了。。。我们来看一下LockIndicator的代码。其实这个小图标的绘制是根据code码来判断图标状态,并没有路径的绘制。(后面又找了一个上面的小图标也能绘制线路的demo,等有时间了再整理起来)
/**
*
* 手势密码图案提示
* @author wulianghuan
*
*/
public class LockIndicator extends View {
private int numRow = 3; // 行
private int numColum = 3; // 列
private int patternWidth = 40;
private int patternHeight = 40;
private int f = 5;
private int g = 5;
private int strokeWidth = 3;
private Paint paint = null;
private Drawable patternNoraml = null;
private Drawable patternPressed = null;
private String lockPassStr; // 手势密码
public LockIndicator(Context paramContext) {
super(paramContext);
}
public LockIndicator(Context paramContext, AttributeSet paramAttributeSet) {
super(paramContext, paramAttributeSet, 0);
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Paint.Style.STROKE);
patternNoraml = getResources().getDrawable(R.drawable.lock_pattern_node_normal);
patternPressed = getResources().getDrawable(R.drawable.lock_pattern_node_pressed);
if (patternPressed != null) {
patternWidth = patternPressed.getIntrinsicWidth();
patternHeight = patternPressed.getIntrinsicHeight();
this.f = (patternWidth / 4);
this.g = (patternHeight / 4);
patternPressed.setBounds(0, 0, patternWidth, patternHeight);
patternNoraml.setBounds(0, 0, patternWidth, patternHeight);
}
}
@Override
protected void onDraw(Canvas canvas) {
if ((patternPressed == null) || (patternNoraml == null)) {
return;
}
// 绘制3*3的图标
for (int i = 0; i < numRow; i++) {
for (int j = 0; j < numColum; j++) {
paint.setColor(0xff0000);
int i1 = j * patternHeight + j * this.g;
int i2 = i * patternWidth + i * this.f;
canvas.save();
canvas.translate(i1, i2);
String curNum = String.valueOf(numColum * i + (j + 1));
if (!TextUtils.isEmpty(lockPassStr)) {
if (lockPassStr.indexOf(curNum) == -1) {
// 未选中
patternNoraml.draw(canvas);
} else {
// 被选中
patternPressed.draw(canvas);
}
} else {
// 重置状态
patternNoraml.draw(canvas);
}
canvas.restore();
}
}
}
@Override
protected void onMeasure(int paramInt1, int paramInt2) {
if (patternPressed != null)
setMeasuredDimension(numColum * patternHeight + this.g
* (-1 + numColum), numRow * patternWidth + this.f
* (-1 + numRow));
}
/**
* 请求重新绘制
* @param paramString 手势密码字符序列
*/
public void setPath(String paramString) {
lockPassStr = paramString;
invalidate();
}
}