最近在学习Android的自定义控件的知识,因为经常见App中有手势锁屏的功能,所以用自定义控件的方法制作了一个简单的手势锁屏的App,(逻辑简单,代码很容易理解)并且添加了一些实际的小功能进行了测试。本来想要制作成一个gif图像在此演示,因为时间的问题就不做了,以下为主要的代码:
启动APP时进入的界面
设置密码时的界面:
密码错误时 :
密码不符合规则时:
大体就是这样的,因为图片资源找的不是很好,所以最终呈现的效果不是很好,但完成了基本的功能。程序总体架构
首先是锁频界面的设计(自定义控件 由 LockPatternView 继承View类)
package com.example.lock;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class LockPatternView extends View{
private static final String TAG = null;
//创建全局对象
judgeflag signal ;
//创建画笔
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
OnpatternchangeListener listener ;
//9个点
private Point points [][]= new Point[3][3];
private boolean isinit , isselected , isover; //判断是否初始化
private float Width, Height, MoveX , MoveY; //获取屏幕的宽,高
private float offsetsX ; //偏移量 X
private float offsetsY; //偏移量 Y
private float BitmapRadious; //图片的半径
private List<Point> listpoint = new ArrayList<Point>(); //用来存放按下的点
private Bitmap normalpoint, errorpoint, pressedpoint , linepoint ,lineerropoint;
private boolean MovingNopoint;
private Matrix matrix = new Matrix(); //矩阵实现图片的缩放,旋转
public LockPatternView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public LockPatternView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
}
public LockPatternView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
//类名前加static,调用不用实例化
public static class Point {
//三种状态
public static int STATE_NORMAL = 0;
public static int STATE_SELECED = 1;
public static int STATE_ERROR = 2;
//点的横坐标,纵坐标
public float x;
public float y;
public int index = 0, state = 0;
public Point(){
}
public Point(float X, float Y){
this.x = X;
this.y = Y;
}
//判断重合的方法,两个点之间的距离在一定的范围内则认为这两个点是一起的,大致认为
public static Boolean with(float x , float y , float R ,float MoveX, float MoveY){
return Math.sqrt( (x - MoveX) * (x - MoveX) + ( y - MoveY) * (y - MoveY) ) < R;
}
//两点之间的距离,用于求缩放比例
public static double distance(Point a ,Point b){
return Math.sqrt( Math.abs( a.x - b.x) * Math.abs(a.x - b.x) + Math.abs(a.y - b.y) * Math.abs(a.y - b.y) ) ;
}
}
/**
* 图案监听器,在OnTouchEvent事件上调用
* @author lenovo
*
*/
//在OnDraw中画出点与线
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//需要初始化点,然后进行绘制
if(!isinit){
initPoints(); //初始化点的函数
}
point2Canvas(canvas); //把点画出来
if(listpoint .size() > 0){ //把线画出来
Point a = listpoint.get(0); //把a点取出
for(int i = 0 ; i < listpoint.size() ; i++){
Point b = listpoint.get(i);
//绘制九宫格中的点
linetoCanvas(canvas, a, b);
a = b;
}
//绘制九宫格之外的坐标点
if(MovingNopoint){
linetoCanvas(canvas, a, new Point(MoveX,MoveY));
}
}
else{
}
}
/**
* 将点绘制在画布上
* @param canvas 画布
*/
private void point2Canvas(Canvas canvas) {
// TODO Auto-generated method stub
for(int i = 0 ; i < points.length ; i++){
for(int j = 0 ; j < points[i].length ; j++){
Point point = points[i][j];
if(point.state == Point.STATE_NORMAL){
canvas.drawBitmap(normalpoint, point.x - BitmapRadious, point.y - BitmapRadious, paint );
}
else if(point.state == Point.STATE_ERROR){
canvas.drawBitmap(errorpoint, point.x - BitmapRadious, point.y - BitmapRadious, paint);
}
else{
canvas.drawBitmap(pressedpoint, point.x - BitmapRadious, point.y - BitmapRadious, paint);
}
}
}
}
/**
*
* @param canvas 画布
* @param a 第一个点
* @param b 第二个点
*/
public void linetoCanvas(Canvas canvas , Point a , Point b){
double linelength = Point.distance(a, b);
float degress = getDegress(a,b);
canvas.rotate(degress, a.x,a.y); //让画布旋转,rotate这个方法旋转的中心是绕着当前点旋转
if(a.state == Point.STATE_SELECED){
//matrix中的两种方法
matrix.setScale((float)linelength / linepoint.getWidth(), 1) ; //x轴,Y轴的缩放 X轴的缩放比例,Y轴不需要缩放则是1,在canvas上能够显示
matrix.postTranslate(a.x - linepoint.getWidth() /2,a.y - linepoint.getHeight() /2); //平移,从当前位置开始画,也就是点击的位置为画的起点
canvas.drawBitmap(linepoint, matrix, paint); //canvas相当于一个透明图层,每次draw时是画在图层上,相当于PS总的图层,然后最终合成在一起
}
else{
matrix.setScale((float)linelength / lineerropoint.getWidth(), 1) ; //x轴,Y轴的缩放 X轴的缩放比例,Y轴不需要缩放则是1
// matrix.postTranslate(a.x - lineerropoint.getWidth() /5,a.y - lineerropoint.getHeight() /5);
matrix.postTranslate(a.x - linepoint.getWidth() /2,a.y - linepoint.getHeight() /2);
// matrix.postTranslate(a.x -7, a.y - 7);
canvas.drawBitmap(lineerropoint, matrix, paint);
}
canvas.rotate( - degress,a.x,a.y); //把画布在旋转回来
}
/**
* 初始化点的函数
*/
private void initPoints() {
// TODO Auto-generated method stub
Width = getWidth(); //获取屏幕宽,高
Height = getHeight();
//判断横屏还是竖屏
if(Width > Height){
//横屏
offsetsX = (Width - Height )/2; //偏移到画圆圈的地方
Width = Height; //图形的宽高一样
}
else{
//竖屏
offsetsY = (Height - Width )/2;
Height = Width;
}
//图片资源
normalpoint = BitmapFactory.decodeResource(getResources(), R.drawable.green);
pressedpoint = BitmapFactory.decodeResource(getResources(), R.drawable.yellow);
errorpoint = BitmapFactory.decodeResource(getResources(), R.drawable.red);
linepoint = BitmapFactory.decodeResource(getResources(), R.drawable.line);
lineerropoint = BitmapFactory.decodeResource(getResources(), R.drawable.lineerror);
//计算点的坐标
points [0][0] = new Point(offsetsX + Width / 4 ,offsetsY+ Width /4);
points [0][1] = new Point(offsetsX + Width /2,offsetsY + Width/4);
points [0][2] = new Point(offsetsX + Width - Width /4 ,offsetsY + Width/4);
points [1][0] = new Point(offsetsX + Width / 4,offsetsY+ Width /2);
points [1][1] = new Point(offsetsX + Width /2,offsetsY+ Width /2);
points [1][2] = new Point(offsetsX + Width - Width /4,offsetsY+ Width /2);
points [2][0] = new Point(offsetsX + Width / 4,offsetsY+ Width - Width /4);
points [2][1] = new Point(offsetsX + Width /2,offsetsY+ Width - Width /4);
points [2][2] = new Point(offsetsX + Width - Width /4,offsetsY+ Width - Width /4);
//图片资源半径,获得当前图片的长度并且除以2
BitmapRadious = normalpoint.getHeight()/2;
//设置密码
int index = 1;
for(Point[] points : this.points){
for(Point point : points){
point.index = index;
index ++ ;
}
}
//初始化完成
isinit = true;
}
//覆盖View中的Touch事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
isover = false;
MovingNopoint = false;
MoveX = event.getX(); //获得当前按下的点的X坐标
MoveY = event.getY(); //获得当前按下的点的Y坐标
Log.i(TAG, MoveX+"");
Log.i(TAG, MoveY+"");
Point point = null ;
switch (event.getAction()) {
//按下
case MotionEvent.ACTION_DOWN :
reset();
if(listener != null){
listener.onpatterstart(true);
}
point = checkpoint();
if(point != null){
isselected = true;
}
break;
//移动
case MotionEvent.ACTION_MOVE:
if(isselected){
point = checkpoint();
if(point == null){
MovingNopoint = true;
}
}
break;
//抬起
case MotionEvent.ACTION_UP:
isover = true;
isselected = false;
break;
}
//选中重复检查
if( !isover && isselected && point !=null){
//交叉点
if(crossPoint(point )){
MovingNopoint = true;
}
//新点
else{
point.state = Point.STATE_SELECED;
listpoint.add(point);
}
}
//绘制结束
if(isover){
//绘制不成立
if(listpoint.size() == 1){
reset();
}
//绘制错误
else if(listpoint.size() < 5 && listpoint.size() > 0){
errorPoint();
if(listener != null){
listener.onpatterchanged(null);
// listener.onpatterchanged("nimei");
}
}
//绘制成功,触发监听事件
else{
if(listener != null){
String passstr = "";
for(int i = 0 ; i < listpoint.size(); i++){
passstr = passstr + listpoint.get(i).index;
}
if(!TextUtils.isEmpty(passstr))
listener.onpatterchanged(passstr); //绘制成功把绘制成功的密码返回出去
// listener.onpatterchanged("nimei");
}
}
}
postInvalidate(); //每次ontouch 事件都必须让View重新绘制一下,刷新View,会重新调用OnDraw的方法
return true;
}
//交叉点的检查
private boolean crossPoint( Point point){
if(listpoint.contains(point)){
return true;
}
else{
return false;
}
}
//获取角度
public float getDegress (Point a ,Point b){
float ax = a.x;
float ay = a.y;
float bx = b.x;
float by = b.y;
float degress = 0;
if(bx == ax){ //y轴相等 90度或270度
if(by > ay){ //在Y轴下边90
degress = 90;
}else if(by < ay){ //在Y轴上边270
degress = 270;
}
}else if(by == ay){ //y轴相等 0 或 180
if(bx > ax){
degress = 0;
}else if(bx < ax){
degress = 180;
}
}
else{
degress = (float) Math.toDegrees((float) Math.atan2(b.y - a.y, b.x - a.x));
}
return degress;
}
//绘制不成立的方法
public void reset(){
for(int i = 0 ; i < listpoint.size() ; i ++){
Point point = listpoint.get(i);
point.state = Point.STATE_NORMAL;
}
listpoint.clear();
}
//绘制错误的方法
public void errorPoint(){
for(Point point : listpoint){
point.state = Point.STATE_ERROR;
}
}
/**
* 判断当前点击的地方的坐标和点的坐标是否相重合(可以有一定范围)
* @return
*/
public Point checkpoint (){
for(int i= 0 ; i < points.length ; i++){
for( int j = 0 ; j < points[i].length ; j++){
Point point = points[i][j];
if(Point.with(point.x, point.y, BitmapRadious, MoveX, MoveY)){
return point;
}
}
}
return null;
}
//判断重合的方法
public static Boolean with(float x , float y , float R ,float MoveX, float MoveY){
return Math.sqrt( (x - MoveX) * (x - MoveX) + ( y - MoveY) * (y - MoveY) ) < R;
}
public static interface OnpatternchangeListener{
//图案改变
void onpatterchanged(String passwordstr); //返回密码字符串
//图案是否重新绘制
void onpatterstart(boolean isstart); //当不画的时候就显示请绘制的字符串
}
public void setOnpatternLListener(OnpatternchangeListener hhh){
if(hhh != null)
this.listener = hhh;
}
}
代码上都有注释,LockPatternView类就实现了锁屏界面的基本功能,看的不是很清楚的可以给我留言,主要的核心代码就是LockPatternView类
然后是欢迎界面WelcomeActivity.java 启动App时,调到锁屏界面,判断如果设置过密码,则这时手势锁屏认为是解锁,如果没有设置过密码,手势锁屏为设置密码(注 :界面之间的传值使用全局对象来完成)
package com.example.lock;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
public class WelcomeActivity extends Activity{
/**
* 欢迎界面
*/
private judgeflag collect;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
//欢迎界面延迟启动
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//检测先前有没有设置过密码,用sharePreerenced
SharedPreferences sp = getSharedPreferences("sp", Context.MODE_PRIVATE);
String password = sp.getString("mima", ""); //从sharepreference中找keypassword所对应的值
if(TextUtils.isEmpty(password)){
//设定密码
Intent intent = new Intent(WelcomeActivity.this,MainActivity.class);
intent.putExtra("judge", "1"); //重新设定密码
startActivity(intent);
finish();
}
//设置过密码,则是密码解锁
else{
collect = (judgeflag)getApplication();
collect.setPassword(password);
startActivity(new Intent(WelcomeActivity.this,MainActivity.class).putExtra("judge", "2")); //解锁密码
finish();
}
}
},1000);
}
}
MainActivity.java用来为锁屏界面添加监听器,监听用户的动作(设置了监听接口,并且使用了Android自带的轻量级存储方法SharedPreference来对密码进行增删改查)
package com.example.lock;
import com.example.lock.LockPatternView.OnpatternchangeListener;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
public class MainActivity extends Activity implements OnpatternchangeListener{
protected static final String TAG = null;
private TextView xianshi;
private LockPatternView hhh ;
private judgeflag judge;
private String chuandimima;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
judge = (judgeflag)getApplication(); //采用全局对象进行界面之间的传值
hhh = (LockPatternView)findViewById(R.id.huatu); //初始化画布
chuandimima = getIntent().getStringExtra("judge");
xianshi = (TextView)findViewById(R.id.name);
hhh.setOnpatternLListener(this); //给图案锁设置监听器
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onpatterchanged(String passwordstr) {
// TODO Auto-generated method stub
if(!TextUtils.isEmpty(passwordstr)){
// xianshi.setText(passwordstr);
// judge.password = passwordstr; //设置密码,把密码写到sharedpreference
if(chuandimima.equals("1")){
SharedPreferences myshare = getSharedPreferences("sp", 0);
SharedPreferences.Editor edit = myshare.edit();
edit.putString("mima", passwordstr); //写入到sharedPreference
edit.commit();
// Log.i(TAG, passwordstr);
xianshi.setText("密码设置成功");
}
else{
if(passwordstr.equals(judge.getPassword()))
{
//xianshi.setText("解锁成功");
Intent intent = new Intent(this,zhujiemian.class);
startActivity(intent);
finish();
}
else
xianshi.setText("手势错误");
}
}
else{
xianshi.setText("至少五个密码");
}
}
@Override
public void onpatterstart(boolean isstart) {
// TODO Auto-generated method stub
if(isstart){
xianshi.setText("请输入图案锁");
}
}
}
主要的核心代码都已经写出,作为一个Android新手,希望大家一起共同学习,大家也可以一起探讨,源码地址如下:
http://download.csdn.net/detail/danielntz/9393180