渤海银行九宫格锁屏示例
2 E币
成为会员,免费下载资料
文件大小:756.05 KB
上传者:lananrun
时间:2015-11-05 17:31:48
下载量:3
本帖最后由 Heguming 于 2015-11-5 22:07 编辑
最近论坛好像很火啊,看到有好多的谭友在这里分享代码,感谢谭主,为大家提供了这么好的一个交流的机会。
好,言归正传,前些天玩的一个九宫格锁屏实例,献给大家。
MainActivity代码较为简单,主要实现向布局中添加九宫格视图,同时根据保存的数据,判断此为第几次输入锁屏密码,并执行相应操作。
[code]
package cn.com.unfind.ges;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;
import android.widget.LinearLayout;
import android.widget.TextView;
import cn.com.unfind.ges.view.NinePointLineView;
/**
* 作用:测试九宫格手势密码 作者:Heguming 时间:2015年11月02日 09:37:54
* */
public class MainActivity extends Activity {
private LinearLayout nine_con;// 九宫格容器
NinePointLineView nv;// 九宫格View
TextView showInfo;
boolean isSetFirst = false;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if (keyCode == KeyEvent.KEYCODE_BACK) {
SharedPreferences shareDate = getSharedPreferences("GUE_PWD", 0);
shareDate.edit().putBoolean("IS_SET", false).commit();
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);// 设置标题不显示
setContentView(R.layout.activity_main);
initWidget();
getSetPwd();
}
private void initWidget() {
nv = new NinePointLineView(MainActivity.this);
nine_con = (LinearLayout) findViewById(R.id.nine_con);
nine_con.addView(nv);
showInfo = (TextView) findViewById(R.id.show_set_info);
}
/**
* 作用:获取现在密码的设置步骤 作者:Heguming 时间:2015年11月02日 14:20:36
* */
public void getSetPwd() {
SharedPreferences shareDate = getSharedPreferences("GUE_PWD", 0);
isSetFirst = shareDate.getBoolean("IS_SET", false);
if (!isSetFirst) {
showInfo.setText("请设置手势密码");
shareDate.edit().clear().commit();
} else {
showInfo.setText("请再次确认手势密码");
}
}
}
[/code]
此处,需要简单介绍一下这三种存储方式的应用场合及使用效率:SharedPreferences、File、SQLite。
一般来说,SharedPreferences与SQLite适合存储需要频繁操作的数据,这两者存储数据类型简单,其构成元素均为基本的int、boolean、String等类型,优点是操作速度快,便于多次读写。
相对于SharedPreferences 与SQLite ,File则更适合操作不频繁的数据,其存储数据类型较为复杂,这也造就了它读写速度较慢的缺点。
而SQLite兼顾简单操作的功能,比如排序、查询等,这是其他两种存储方式不能比拟的,但也因此导致SQLite使用方法较为复杂,需要经过建库、建表等多个步骤,还需要定期进行更新与维护。
最后一个不同是,SQLite与File适合存储大数据量的操作,SharedPreferences则更适合存储小数据量的操作。
下面,就是关键代码了---NinePointLineView,此部分代码通过覆写onMeasure方法与onLayout方法,实现将九宫格视图添加至当前View,同时通过覆写onTouchEvent方法与onDraw方法监听屏幕输入,并对绘制视图进行实时更新。
[code]
package cn.com.unfind.ges.view;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import cn.com.unfind.ges.MainActivity;
import cn.com.unfind.ges.R;
/**
* 作用:手势密码的九宫格 作者:ufnind 时间:2015年11月03日 12:34:52
* */
public class NinePointLineView extends View {
Paint linePaint = new Paint();
Paint whiteLinePaint = new Paint();
Bitmap defaultBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.lock);
int defaultBitmapRadius = defaultBitmap.getWidth() / 2;
Bitmap selectedBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.indicator_lock_area);
int selectedBitmapDiameter = selectedBitmap.getWidth();
int selectedBitmapRadius = selectedBitmapDiameter / 2;
PointInfo[] points = new PointInfo[9];
PointInfo startPoint = null;
int width, height;
int moveX, moveY;
boolean isUp = false;
Context cxt;
StringBuffer lockString = new StringBuffer();
public NinePointLineView(Context context) {
super(context);
cxt = context;
initPaint();
}
public NinePointLineView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
private void initPaint() {
initLinePaint(linePaint);
initWhiteLinePaint(whiteLinePaint);
}
/**
* @param paint
*/
private void initLinePaint(Paint paint) {
paint.setColor(Color.GRAY);
paint.setStrokeWidth(30);// 设置两个点之间的线的背景的宽度
paint.setAntiAlias(true);
paint.setStrokeCap(Cap.ROUND);
}
/**
* @param paint
*/
private void initWhiteLinePaint(Paint paint) {
paint.setColor(Color.WHITE);
paint.setStrokeWidth(20);// 设置两个点之间的线的宽度
paint.setAntiAlias(true);
paint.setStrokeCap(Cap.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
width = getWidth();
height = getHeight();
if (width != 0 && height != 0) {
initPoints(points);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void initPoints(PointInfo[] points) {
int len = points.length;
int seletedSpacing = (width - selectedBitmapDiameter * 3) / 4;
int seletedX = seletedSpacing;
int seletedY = height - width + seletedSpacing;
int defaultX = seletedX + selectedBitmapRadius - defaultBitmapRadius;
int defaultY = seletedY + selectedBitmapRadius - defaultBitmapRadius;
for (int i = 0; i < len; i++) {
if (i == 3 || i == 6) {
seletedX = seletedSpacing;
seletedY += selectedBitmapDiameter + seletedSpacing;
defaultX = seletedX + selectedBitmapRadius
- defaultBitmapRadius;
defaultY += selectedBitmapDiameter + seletedSpacing;
}
points = new PointInfo(i, defaultX, defaultY, seletedX, seletedY);
seletedX += selectedBitmapDiameter + seletedSpacing;
defaultX += selectedBitmapDiameter + seletedSpacing;
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
drawNinePoint(canvas);
super.onDraw(canvas);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean flag = true;
if (isUp) {
finishDraw();
flag = false;
} else {
handlingEvent(event);
flag = true;
}
return flag;
}
private void handlingEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
moveX = (int) event.getX();
moveY = (int) event.getY();
for (PointInfo temp : points) {
if (temp.isInMyPlace(moveX, moveY)
&& temp.isSelected() == false) {
temp.setSelected(true);
int len = lockString.length();
if (len != 0) {
int preId = lockString.charAt(len - 1) - 48;
points[preId].setNextId(temp.getId());
}
lockString.append(temp.getId());
break;
}
}
invalidate(0, height - width, width, height);
break;
case MotionEvent.ACTION_DOWN:
int downX = (int) event.getX();
int downY = (int) event.getY();
for (PointInfo temp : points) {
if (temp != null) {
if (temp.isInMyPlace(downX, downY)) {
temp.setSelected(true);
startPoint = temp;
lockString.append(temp.getId());
break;
}
}
}
invalidate(0, height - width, width, height);
break;
case MotionEvent.ACTION_UP:
isUp = true;
invalidate();
savePwd();
break;
default:
break;
}
}
private void finishDraw() {
for (PointInfo temp : points) {
temp.setSelected(false);
temp.setNextId(temp.getId());
}
lockString.delete(0, lockString.length());
isUp = false;
invalidate();
}
/**
*
* @param canvas
*/
private void drawNinePoint(Canvas canvas) {
if (startPoint != null) {
drawEachLine(canvas, startPoint);
}
for (PointInfo pointInfo : points) {
if (pointInfo != null) {
if (pointInfo.isSelected()) {
canvas.drawBitmap(selectedBitmap, pointInfo.getSeletedX(),
pointInfo.getSeletedY(), null);
}
canvas.drawBitmap(defaultBitmap, pointInfo.getDefaultX(),
pointInfo.getDefaultY(), null);
}
}
}
/**
* @param canvas
* @param point
*/
private void drawEachLine(Canvas canvas, PointInfo point) {
if (point.hasNextId()) {
int n = point.getNextId();
drawLine(canvas, point.getCenterX(), point.getCenterY(),
points[n].getCenterX(), points[n].getCenterY());
drawEachLine(canvas, points[n]);
}
}
/**
*
* @param canvas
* @param startX
* @param startY
* @param stopX
* @param stopY
*/
private void drawLine(Canvas canvas, float startX, float startY,
float stopX, float stopY) {
canvas.drawLine(startX, startY, stopX, stopY, linePaint);
canvas.drawLine(startX, startY, stopX, stopY, whiteLinePaint);
}
/**
* @author zkwlx
*
*/
private class PointInfo {
private int id;
private int nextId;
private boolean selected;
private int defaultX;
private int defaultY;
private int seletedX;
private int seletedY;
public PointInfo(int id, int defaultX, int defaultY, int seletedX,
int seletedY) {
this.id = id;
this.nextId = id;
this.defaultX = defaultX;
this.defaultY = defaultY;
this.seletedX = seletedX;
this.seletedY = seletedY;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public int getId() {
return id;
}
public int getDefaultX() {
return defaultX;
}
public int getDefaultY() {
return defaultY;
}
public int getSeletedX() {
return seletedX;
}
public int getSeletedY() {
return seletedY;
}
public int getCenterX() {
return seletedX + selectedBitmapRadius;
}
public int getCenterY() {
return seletedY + selectedBitmapRadius;
}
public boolean hasNextId() {
return nextId != id;
}
public int getNextId() {
return nextId;
}
public void setNextId(int nextId) {
this.nextId = nextId;
}
/**
* @param x
* @param y
*/
public boolean isInMyPlace(int x, int y) {
boolean inX = x > seletedX
&& x < (seletedX + selectedBitmapDiameter);
boolean inY = y > seletedY
&& y < (seletedY + selectedBitmapDiameter);
return (inX && inY);
}
}
public String getPwd() {// 获取本次的密码
return lockString.toString();
}
/**
* 作用:保存密码并且判断界面的跳转 作者:Heguming 时间:2015年11月02日 14:47:47
* */
public void savePwd() {
Intent intent = new Intent();
SharedPreferences shareDate = cxt.getSharedPreferences("GUE_PWD", 0);
boolean isSetFirst = shareDate.getBoolean("IS_SET", false);
if (isSetFirst) {// 如果第一次已经设置密码,验证第二次和第一次是否一致
String pwd = this.getPwd();
String first_pwd = shareDate.getString("FIRST_PWD", "NO HAVE PWD");
if (pwd.equals(first_pwd)) {// 第二次密码和第一次密码一样 设置成功
shareDate.edit().clear().commit();
intent.setClass(cxt, MainActivity.class);
Toast.makeText(cxt, "您输入的密码为:" + pwd, Toast.LENGTH_SHORT)
.show();
} else {// 第二次输入的密码和第一次输入的密码不一致
Toast.makeText(cxt, "和第一次输入手势密码不一致,重新输入", Toast.LENGTH_SHORT)
.show();
intent.setClass(cxt, MainActivity.class);
}
shareDate.edit().putBoolean("IS_SET", false).commit();
} else {// 第一次设置手势密码
shareDate.edit().clear().commit();
shareDate.edit().putString("FIRST_PWD", this.getPwd()).commit();
shareDate.edit().putBoolean("IS_SET", true).commit();
intent.setClass(cxt, MainActivity.class);
}
cxt.startActivity(intent);
((Activity) cxt).finish();
}
}
[/code]
以下,对该代码进行简要分析。
首先,需要说明一下PointInfo类,该类相当于MVC模式(MVC:Model/模型-View/视图-Controller/控制器)中的M,即模型,其内部包含五个构造参数:id,defaultX,defaultY,seletedX,seletedY。
id:九宫格中该点的序列号:0~8
defaultX:未选中状态下该点的横坐标
defaultY:未选中状态下该点的纵坐标
seletedX:选中状态下该点的横坐标
seletedY:选中状态下该点的纵坐标
同时,该类还包含了两个局部变量:nextId---下个被选点的序列号,selected---该点是否被选中,以及一个普通方法:isInMyPlace(),用于判断该点的选中范围,即什么范围内,可认为该点被选中。
接下来,对NinePointLineView类中的变量作下说明:
linePaint、whiteLinePaint---画笔对象,用于绘制连接线。
defaultBitmap、defaultBitmapRadius---Bitmap对象,分别为默认状态及选中状态下九宫格中各点的图片。
defaultBitmapRadius、selectedBitmapDiameter、selectedBitmapRadius:分别为九宫格中各点默认状态下的半径、选中状态下的直径及选中状态下的半径。
很明显,defaultBitmapRadius = defaultBitmap.getWidth() / 2, selectedBitmapDiameter = selectedBitmap.getWidth(), selectedBitmapRadius = selectedBitmapDiameter / 2。
points---PointInfo对象点的集合。
startPoint---开始绘制时的起始点。
width、height---九宫格视图的宽度及高度。
moveX、moveY: onTouchEvent事件中,经过移动后的该点的横坐标、纵坐标。
isUp: onTouchEvent事件中,用于判断触点是否离开屏幕。
lockString:将输入密码转换为序列号集合的字符串。
cxt:上下文对象。
下面,描述下该类的工作流程,并简要叙述下各个方法的功能。
首先,在构造方法中,引入上下文对象并赋值,同时通过initPaint()方法初始化画笔。
initPaint()方法中主要执行两个功能,其一,通过initLinePaint()方法,设置两个点之间连线背景的相关属性,即连线中的灰色背景的属性。其二,通过initWhiteLinePaint()方法,设置两个点之间连线前景的相关属性,即连线中白色前景的属性。二者的画笔属性主要包括颜色(Paint.setColor(Color.GRAY))、宽度(Paint.setStrokeWidth(30))、锯齿效果(Paint.setAntiAlias(true))、笔刷样式(Paint.setStrokeCap(Cap.ROUND))在内的四个部分。
然后,通过onMeasure()方法,计算该九宫格视图所占的宽度及高度,并赋值给width、height,同时通过initPoints(PointInfo[] points)方法,初始化points集合中的各点。
此处,需要详细说明下initPoints(PointInfo[] points)方法:
该方法需要将九个点分别转换为PointInfo对象,之前讲过,PointInfo对象需要5个构造参数---id、defaultX、defaultY、seletedX、seletedY,故而需要计算出每个点的相关属性。
第一步,计算选中状态下每两个点之间的间隔---seletedSpacing = (width - selectedBitmapDiameter * 3) / 4,即: (九宫格视图宽度 - 选中状态下各点的直径 * 3)/4。
第二步,循环计算在默认状态与选中状态下,各点的横坐标与纵坐标: seletedX、seletedY、defaultX、defaultY。
很明显,seletedX = id % 3 * selectedBitmapDiameter + (id % 3 + 1) * seletedSpacing
defaultX = id % 3 * selectedBitmapDiameter + (id % 3 + 1) * seletedSpacing + selectedBitmapRadius - defaultBitmapRadius
seletedY = id / 3 * selectedBitmapDiameter + (id / 3 + 1) * seletedSpacing
defaultY = id % 3 * selectedBitmapDiameter + (id / 3 + 1) * seletedSpacing + selectedBitmapRadius - defaultBitmapRadius
最后一步,分别实例化PointInfo对象,并将其添加至points集合。
接下来,通过onLayout()方法确定布局位置,该方法内部直接调用父类onLayout()方法即可。
然后,通过onTouchEvent()监听屏幕输入,然后通过onDraw()方法对每次屏幕输入进行视图绘制。
在onTouchEvent()中,判断触点是否离开屏幕,即判断isUp是否为true。如果为true,即通过finishDraw()方法结束视图绘制,同时将flag置为false,否则,通过handlingEvent()方法对屏幕输入事件进行同时将flag置为true,通过返回flag标志位,系统可以判断是否对继续相应下一Touch事件(若返回为true,则继续执行onTouchEvent,否则终止执行,很明显,当isUp为true时,即触点离开屏幕时,终止onTouchEvent的执行)。
对于finishDraw()方法,没什么需要介绍的,就是终止视图的绘制, 将points中所有点置位,即 setSelected(false)(设置选中状态为false)、setNextId(temp.getId())(设置下一选中点为它本身),同时刷新视图。
对于handlingEvent()方法,其主要功能是根据event.getAction()方法获取MotionEvent常量,并根据常量值分别对event进行处理:
MotionEvent.ACTION_DOWN---获取当前触点坐标,通过PointInfo.isInMyPlace()方法,根据坐标值,判断各点状态范围,即是否处于被选中状态范围之内,如果是,则设置该点为选中状态,同时赋值给startPoint,并将该点序列号以字符形式赋值给lockString,最后刷新视图。
MotionEvent.ACTION_MOVE---获取当前触点坐标,通过PointInfo.isInMyPlace()方法,根据坐标值,判断各点状态范围,即是否处于被选中状态范围之内,如果是,还需判断该点是否已被选中,即强制同一点不可被重复选中。如果未曾选中过,则设置该点为选中状态,并将该点序列号以字符形式追加给lockString,同时设置上一选中点的nextId属性: points[preId].setNextId(temp.getId()), 即为该点的Id,最后刷新视图。
MotionEvent.ACTION_UP---通过savePwd()方法保存九宫格密码:lockString,同时刷新视图。
对于onDraw()方法,它在每次视图改变时,都会进行重新绘制。在该方法内部,主要通过drawNinePoint()方法对九宫格每一点进行绘制。
在drawNinePoint()方法中,先判断startPoint 是否为空,即是否开始绘制锁屏图案。如果非空,通过drawEachLine()方法,根据每个点的Id属性与nextId属性,分别绘制这两个点之间的连线,即白色前景连线与灰色背景连线。后根据各个点的状态,绘制选中状态下的Bitmap,或是非选中状态下的Bitmap,即canvas.drawBitmap(selectedBitmap, pointInfo.getSeletedX(),pointInfo.getSeletedY(), null)、canvas.drawBitmap(defaultBitmap, pointInfo.getDefaultX(),pointInfo.getDefaultY(), null)( 此处Canvas为画布对象,读者可自行摆渡 )。
对于savePwd()方法,该方法主要用于密码保存及密码验证。通过isSetFirst变量,判断此为第几次输入锁屏密码。如果为第一次,则保存该密码,同时启动MainActivity,再次输入锁屏密码。如果为第二次,则判断两次密码输入是否一致,如果一致,则保存该密码为最终密码,否则,提示用户两次密码输入不同,请重新输入。
至此,所有代码分析完毕,效果如下:
[attach]3005[/attach]
[attach]3006[/attach]
最后,附上程序代码(见附件) :)
原创,转载请注明出处
[attach]3007[/attach]
最近论坛好像很火啊,看到有好多的谭友在这里分享代码,感谢谭主,为大家提供了这么好的一个交流的机会。
好,言归正传,前些天玩的一个九宫格锁屏实例,献给大家。
MainActivity代码较为简单,主要实现向布局中添加九宫格视图,同时根据保存的数据,判断此为第几次输入锁屏密码,并执行相应操作。
[code]
package cn.com.unfind.ges;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;
import android.widget.LinearLayout;
import android.widget.TextView;
import cn.com.unfind.ges.view.NinePointLineView;
/**
* 作用:测试九宫格手势密码 作者:Heguming 时间:2015年11月02日 09:37:54
* */
public class MainActivity extends Activity {
private LinearLayout nine_con;// 九宫格容器
NinePointLineView nv;// 九宫格View
TextView showInfo;
boolean isSetFirst = false;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if (keyCode == KeyEvent.KEYCODE_BACK) {
SharedPreferences shareDate = getSharedPreferences("GUE_PWD", 0);
shareDate.edit().putBoolean("IS_SET", false).commit();
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);// 设置标题不显示
setContentView(R.layout.activity_main);
initWidget();
getSetPwd();
}
private void initWidget() {
nv = new NinePointLineView(MainActivity.this);
nine_con = (LinearLayout) findViewById(R.id.nine_con);
nine_con.addView(nv);
showInfo = (TextView) findViewById(R.id.show_set_info);
}
/**
* 作用:获取现在密码的设置步骤 作者:Heguming 时间:2015年11月02日 14:20:36
* */
public void getSetPwd() {
SharedPreferences shareDate = getSharedPreferences("GUE_PWD", 0);
isSetFirst = shareDate.getBoolean("IS_SET", false);
if (!isSetFirst) {
showInfo.setText("请设置手势密码");
shareDate.edit().clear().commit();
} else {
showInfo.setText("请再次确认手势密码");
}
}
}
[/code]
此处,需要简单介绍一下这三种存储方式的应用场合及使用效率:SharedPreferences、File、SQLite。
一般来说,SharedPreferences与SQLite适合存储需要频繁操作的数据,这两者存储数据类型简单,其构成元素均为基本的int、boolean、String等类型,优点是操作速度快,便于多次读写。
相对于SharedPreferences 与SQLite ,File则更适合操作不频繁的数据,其存储数据类型较为复杂,这也造就了它读写速度较慢的缺点。
而SQLite兼顾简单操作的功能,比如排序、查询等,这是其他两种存储方式不能比拟的,但也因此导致SQLite使用方法较为复杂,需要经过建库、建表等多个步骤,还需要定期进行更新与维护。
最后一个不同是,SQLite与File适合存储大数据量的操作,SharedPreferences则更适合存储小数据量的操作。
下面,就是关键代码了---NinePointLineView,此部分代码通过覆写onMeasure方法与onLayout方法,实现将九宫格视图添加至当前View,同时通过覆写onTouchEvent方法与onDraw方法监听屏幕输入,并对绘制视图进行实时更新。
[code]
package cn.com.unfind.ges.view;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import cn.com.unfind.ges.MainActivity;
import cn.com.unfind.ges.R;
/**
* 作用:手势密码的九宫格 作者:ufnind 时间:2015年11月03日 12:34:52
* */
public class NinePointLineView extends View {
Paint linePaint = new Paint();
Paint whiteLinePaint = new Paint();
Bitmap defaultBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.lock);
int defaultBitmapRadius = defaultBitmap.getWidth() / 2;
Bitmap selectedBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.indicator_lock_area);
int selectedBitmapDiameter = selectedBitmap.getWidth();
int selectedBitmapRadius = selectedBitmapDiameter / 2;
PointInfo[] points = new PointInfo[9];
PointInfo startPoint = null;
int width, height;
int moveX, moveY;
boolean isUp = false;
Context cxt;
StringBuffer lockString = new StringBuffer();
public NinePointLineView(Context context) {
super(context);
cxt = context;
initPaint();
}
public NinePointLineView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
private void initPaint() {
initLinePaint(linePaint);
initWhiteLinePaint(whiteLinePaint);
}
/**
* @param paint
*/
private void initLinePaint(Paint paint) {
paint.setColor(Color.GRAY);
paint.setStrokeWidth(30);// 设置两个点之间的线的背景的宽度
paint.setAntiAlias(true);
paint.setStrokeCap(Cap.ROUND);
}
/**
* @param paint
*/
private void initWhiteLinePaint(Paint paint) {
paint.setColor(Color.WHITE);
paint.setStrokeWidth(20);// 设置两个点之间的线的宽度
paint.setAntiAlias(true);
paint.setStrokeCap(Cap.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
width = getWidth();
height = getHeight();
if (width != 0 && height != 0) {
initPoints(points);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void initPoints(PointInfo[] points) {
int len = points.length;
int seletedSpacing = (width - selectedBitmapDiameter * 3) / 4;
int seletedX = seletedSpacing;
int seletedY = height - width + seletedSpacing;
int defaultX = seletedX + selectedBitmapRadius - defaultBitmapRadius;
int defaultY = seletedY + selectedBitmapRadius - defaultBitmapRadius;
for (int i = 0; i < len; i++) {
if (i == 3 || i == 6) {
seletedX = seletedSpacing;
seletedY += selectedBitmapDiameter + seletedSpacing;
defaultX = seletedX + selectedBitmapRadius
- defaultBitmapRadius;
defaultY += selectedBitmapDiameter + seletedSpacing;
}
points = new PointInfo(i, defaultX, defaultY, seletedX, seletedY);
seletedX += selectedBitmapDiameter + seletedSpacing;
defaultX += selectedBitmapDiameter + seletedSpacing;
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
drawNinePoint(canvas);
super.onDraw(canvas);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean flag = true;
if (isUp) {
finishDraw();
flag = false;
} else {
handlingEvent(event);
flag = true;
}
return flag;
}
private void handlingEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
moveX = (int) event.getX();
moveY = (int) event.getY();
for (PointInfo temp : points) {
if (temp.isInMyPlace(moveX, moveY)
&& temp.isSelected() == false) {
temp.setSelected(true);
int len = lockString.length();
if (len != 0) {
int preId = lockString.charAt(len - 1) - 48;
points[preId].setNextId(temp.getId());
}
lockString.append(temp.getId());
break;
}
}
invalidate(0, height - width, width, height);
break;
case MotionEvent.ACTION_DOWN:
int downX = (int) event.getX();
int downY = (int) event.getY();
for (PointInfo temp : points) {
if (temp != null) {
if (temp.isInMyPlace(downX, downY)) {
temp.setSelected(true);
startPoint = temp;
lockString.append(temp.getId());
break;
}
}
}
invalidate(0, height - width, width, height);
break;
case MotionEvent.ACTION_UP:
isUp = true;
invalidate();
savePwd();
break;
default:
break;
}
}
private void finishDraw() {
for (PointInfo temp : points) {
temp.setSelected(false);
temp.setNextId(temp.getId());
}
lockString.delete(0, lockString.length());
isUp = false;
invalidate();
}
/**
*
* @param canvas
*/
private void drawNinePoint(Canvas canvas) {
if (startPoint != null) {
drawEachLine(canvas, startPoint);
}
for (PointInfo pointInfo : points) {
if (pointInfo != null) {
if (pointInfo.isSelected()) {
canvas.drawBitmap(selectedBitmap, pointInfo.getSeletedX(),
pointInfo.getSeletedY(), null);
}
canvas.drawBitmap(defaultBitmap, pointInfo.getDefaultX(),
pointInfo.getDefaultY(), null);
}
}
}
/**
* @param canvas
* @param point
*/
private void drawEachLine(Canvas canvas, PointInfo point) {
if (point.hasNextId()) {
int n = point.getNextId();
drawLine(canvas, point.getCenterX(), point.getCenterY(),
points[n].getCenterX(), points[n].getCenterY());
drawEachLine(canvas, points[n]);
}
}
/**
*
* @param canvas
* @param startX
* @param startY
* @param stopX
* @param stopY
*/
private void drawLine(Canvas canvas, float startX, float startY,
float stopX, float stopY) {
canvas.drawLine(startX, startY, stopX, stopY, linePaint);
canvas.drawLine(startX, startY, stopX, stopY, whiteLinePaint);
}
/**
* @author zkwlx
*
*/
private class PointInfo {
private int id;
private int nextId;
private boolean selected;
private int defaultX;
private int defaultY;
private int seletedX;
private int seletedY;
public PointInfo(int id, int defaultX, int defaultY, int seletedX,
int seletedY) {
this.id = id;
this.nextId = id;
this.defaultX = defaultX;
this.defaultY = defaultY;
this.seletedX = seletedX;
this.seletedY = seletedY;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public int getId() {
return id;
}
public int getDefaultX() {
return defaultX;
}
public int getDefaultY() {
return defaultY;
}
public int getSeletedX() {
return seletedX;
}
public int getSeletedY() {
return seletedY;
}
public int getCenterX() {
return seletedX + selectedBitmapRadius;
}
public int getCenterY() {
return seletedY + selectedBitmapRadius;
}
public boolean hasNextId() {
return nextId != id;
}
public int getNextId() {
return nextId;
}
public void setNextId(int nextId) {
this.nextId = nextId;
}
/**
* @param x
* @param y
*/
public boolean isInMyPlace(int x, int y) {
boolean inX = x > seletedX
&& x < (seletedX + selectedBitmapDiameter);
boolean inY = y > seletedY
&& y < (seletedY + selectedBitmapDiameter);
return (inX && inY);
}
}
public String getPwd() {// 获取本次的密码
return lockString.toString();
}
/**
* 作用:保存密码并且判断界面的跳转 作者:Heguming 时间:2015年11月02日 14:47:47
* */
public void savePwd() {
Intent intent = new Intent();
SharedPreferences shareDate = cxt.getSharedPreferences("GUE_PWD", 0);
boolean isSetFirst = shareDate.getBoolean("IS_SET", false);
if (isSetFirst) {// 如果第一次已经设置密码,验证第二次和第一次是否一致
String pwd = this.getPwd();
String first_pwd = shareDate.getString("FIRST_PWD", "NO HAVE PWD");
if (pwd.equals(first_pwd)) {// 第二次密码和第一次密码一样 设置成功
shareDate.edit().clear().commit();
intent.setClass(cxt, MainActivity.class);
Toast.makeText(cxt, "您输入的密码为:" + pwd, Toast.LENGTH_SHORT)
.show();
} else {// 第二次输入的密码和第一次输入的密码不一致
Toast.makeText(cxt, "和第一次输入手势密码不一致,重新输入", Toast.LENGTH_SHORT)
.show();
intent.setClass(cxt, MainActivity.class);
}
shareDate.edit().putBoolean("IS_SET", false).commit();
} else {// 第一次设置手势密码
shareDate.edit().clear().commit();
shareDate.edit().putString("FIRST_PWD", this.getPwd()).commit();
shareDate.edit().putBoolean("IS_SET", true).commit();
intent.setClass(cxt, MainActivity.class);
}
cxt.startActivity(intent);
((Activity) cxt).finish();
}
}
[/code]
以下,对该代码进行简要分析。
首先,需要说明一下PointInfo类,该类相当于MVC模式(MVC:Model/模型-View/视图-Controller/控制器)中的M,即模型,其内部包含五个构造参数:id,defaultX,defaultY,seletedX,seletedY。
id:九宫格中该点的序列号:0~8
defaultX:未选中状态下该点的横坐标
defaultY:未选中状态下该点的纵坐标
seletedX:选中状态下该点的横坐标
seletedY:选中状态下该点的纵坐标
同时,该类还包含了两个局部变量:nextId---下个被选点的序列号,selected---该点是否被选中,以及一个普通方法:isInMyPlace(),用于判断该点的选中范围,即什么范围内,可认为该点被选中。
接下来,对NinePointLineView类中的变量作下说明:
linePaint、whiteLinePaint---画笔对象,用于绘制连接线。
defaultBitmap、defaultBitmapRadius---Bitmap对象,分别为默认状态及选中状态下九宫格中各点的图片。
defaultBitmapRadius、selectedBitmapDiameter、selectedBitmapRadius:分别为九宫格中各点默认状态下的半径、选中状态下的直径及选中状态下的半径。
很明显,defaultBitmapRadius = defaultBitmap.getWidth() / 2, selectedBitmapDiameter = selectedBitmap.getWidth(), selectedBitmapRadius = selectedBitmapDiameter / 2。
points---PointInfo对象点的集合。
startPoint---开始绘制时的起始点。
width、height---九宫格视图的宽度及高度。
moveX、moveY: onTouchEvent事件中,经过移动后的该点的横坐标、纵坐标。
isUp: onTouchEvent事件中,用于判断触点是否离开屏幕。
lockString:将输入密码转换为序列号集合的字符串。
cxt:上下文对象。
下面,描述下该类的工作流程,并简要叙述下各个方法的功能。
首先,在构造方法中,引入上下文对象并赋值,同时通过initPaint()方法初始化画笔。
initPaint()方法中主要执行两个功能,其一,通过initLinePaint()方法,设置两个点之间连线背景的相关属性,即连线中的灰色背景的属性。其二,通过initWhiteLinePaint()方法,设置两个点之间连线前景的相关属性,即连线中白色前景的属性。二者的画笔属性主要包括颜色(Paint.setColor(Color.GRAY))、宽度(Paint.setStrokeWidth(30))、锯齿效果(Paint.setAntiAlias(true))、笔刷样式(Paint.setStrokeCap(Cap.ROUND))在内的四个部分。
然后,通过onMeasure()方法,计算该九宫格视图所占的宽度及高度,并赋值给width、height,同时通过initPoints(PointInfo[] points)方法,初始化points集合中的各点。
此处,需要详细说明下initPoints(PointInfo[] points)方法:
该方法需要将九个点分别转换为PointInfo对象,之前讲过,PointInfo对象需要5个构造参数---id、defaultX、defaultY、seletedX、seletedY,故而需要计算出每个点的相关属性。
第一步,计算选中状态下每两个点之间的间隔---seletedSpacing = (width - selectedBitmapDiameter * 3) / 4,即: (九宫格视图宽度 - 选中状态下各点的直径 * 3)/4。
第二步,循环计算在默认状态与选中状态下,各点的横坐标与纵坐标: seletedX、seletedY、defaultX、defaultY。
很明显,seletedX = id % 3 * selectedBitmapDiameter + (id % 3 + 1) * seletedSpacing
defaultX = id % 3 * selectedBitmapDiameter + (id % 3 + 1) * seletedSpacing + selectedBitmapRadius - defaultBitmapRadius
seletedY = id / 3 * selectedBitmapDiameter + (id / 3 + 1) * seletedSpacing
defaultY = id % 3 * selectedBitmapDiameter + (id / 3 + 1) * seletedSpacing + selectedBitmapRadius - defaultBitmapRadius
最后一步,分别实例化PointInfo对象,并将其添加至points集合。
接下来,通过onLayout()方法确定布局位置,该方法内部直接调用父类onLayout()方法即可。
然后,通过onTouchEvent()监听屏幕输入,然后通过onDraw()方法对每次屏幕输入进行视图绘制。
在onTouchEvent()中,判断触点是否离开屏幕,即判断isUp是否为true。如果为true,即通过finishDraw()方法结束视图绘制,同时将flag置为false,否则,通过handlingEvent()方法对屏幕输入事件进行同时将flag置为true,通过返回flag标志位,系统可以判断是否对继续相应下一Touch事件(若返回为true,则继续执行onTouchEvent,否则终止执行,很明显,当isUp为true时,即触点离开屏幕时,终止onTouchEvent的执行)。
对于finishDraw()方法,没什么需要介绍的,就是终止视图的绘制, 将points中所有点置位,即 setSelected(false)(设置选中状态为false)、setNextId(temp.getId())(设置下一选中点为它本身),同时刷新视图。
对于handlingEvent()方法,其主要功能是根据event.getAction()方法获取MotionEvent常量,并根据常量值分别对event进行处理:
MotionEvent.ACTION_DOWN---获取当前触点坐标,通过PointInfo.isInMyPlace()方法,根据坐标值,判断各点状态范围,即是否处于被选中状态范围之内,如果是,则设置该点为选中状态,同时赋值给startPoint,并将该点序列号以字符形式赋值给lockString,最后刷新视图。
MotionEvent.ACTION_MOVE---获取当前触点坐标,通过PointInfo.isInMyPlace()方法,根据坐标值,判断各点状态范围,即是否处于被选中状态范围之内,如果是,还需判断该点是否已被选中,即强制同一点不可被重复选中。如果未曾选中过,则设置该点为选中状态,并将该点序列号以字符形式追加给lockString,同时设置上一选中点的nextId属性: points[preId].setNextId(temp.getId()), 即为该点的Id,最后刷新视图。
MotionEvent.ACTION_UP---通过savePwd()方法保存九宫格密码:lockString,同时刷新视图。
对于onDraw()方法,它在每次视图改变时,都会进行重新绘制。在该方法内部,主要通过drawNinePoint()方法对九宫格每一点进行绘制。
在drawNinePoint()方法中,先判断startPoint 是否为空,即是否开始绘制锁屏图案。如果非空,通过drawEachLine()方法,根据每个点的Id属性与nextId属性,分别绘制这两个点之间的连线,即白色前景连线与灰色背景连线。后根据各个点的状态,绘制选中状态下的Bitmap,或是非选中状态下的Bitmap,即canvas.drawBitmap(selectedBitmap, pointInfo.getSeletedX(),pointInfo.getSeletedY(), null)、canvas.drawBitmap(defaultBitmap, pointInfo.getDefaultX(),pointInfo.getDefaultY(), null)( 此处Canvas为画布对象,读者可自行摆渡 )。
对于savePwd()方法,该方法主要用于密码保存及密码验证。通过isSetFirst变量,判断此为第几次输入锁屏密码。如果为第一次,则保存该密码,同时启动MainActivity,再次输入锁屏密码。如果为第二次,则判断两次密码输入是否一致,如果一致,则保存该密码为最终密码,否则,提示用户两次密码输入不同,请重新输入。
至此,所有代码分析完毕,效果如下:
[attach]3005[/attach]
[attach]3006[/attach]
最后,附上程序代码(见附件) :)
原创,转载请注明出处
[attach]3007[/attach]
展开》
折叠》