先看效果图:
效果图.gif
1.加入背景图,以及滑动解锁的点
(1) 背景图由于只有一张,所以我们选择用xml文件配置
android:id="@+id/iv_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/bg"
android:scaleType="center"/>
此部分特别简单,不做过多赘述
(2)加入点,我们需要插入很多个点,xml文件可以配置,但是重复代码太多,造成代码臃肿,所以我们选用代码加入
private void initNineDot(int res,int visiable,boolean record) {
rl = findViewById(R.id.rl_root);//这是我们需要加入的父视图
padding = pixelFromDp(40);//像素值的转换
//计算两点中心点之间的距离
Point p = new Point();
getWindowManager().getDefaultDisplay().getSize(p);
//获取图片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.normal);
float space = (p.x - 2 * padding - bitmap.getWidth()) / 2;
//计算起始点的坐标
float x = padding;
float y = p.y / 2 - space - bitmap.getHeight();
if (!record) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
creatDot(res, (int) (y + space * i), (int) (x + space * j), visiable,0);
}
}
}else{
int index=1;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
creatDot(res, (int) (y + space * i), (int) (x + space * j), visiable,index);
index++;
}
}
}
}
getWindowManager().getDefaultDisplay().getSize(p):获取当前屏幕的尺寸
record:布尔类型,用来判断是否需要在该点加入tag值,加入tag值是为了为方便我们在滑动的过程中记录划过的点,以便于把密码记下来
下面是加入点的关键代码:
rivate void creatDot(int res,int top,int left,int visiable,int index){
ImageView selected=new ImageView(this);
selected.setBackgroundResource(res);
selected.setTag(index);
RelativeLayout.LayoutParams parmas=new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
parmas.leftMargin= left;
parmas.topMargin= top;
selected.setVisibility(visiable);
rl.addView(selected,parmas);
//判断res是否是selected 则要加入ImageView数组中
if(res==R.drawable.selected){
dotViews.add(selected);
}
}
ImageView.addView()方法中需传递两个参数,一个子控件,一个是位置参数;
接下里关于onTouch方法来获取密码的我们创建一个新的类
先贴上MainActivity类中的onCreat()方法:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dotViews=new ArrayList<>();
setContentView(R.layout.activity_main);
//初始化9个正常点
initNineDot(R.drawable.normal, View.VISIBLE,false);
initDrawView();
//初始化9个高光点
initNineDot(R.drawable.selected,View.INVISIBLE,true);
//将所以点的数组 转递给DrawView
dv.setDotViews(dotViews);
TextView textView=findViewById(R.id.tv_alertText);
dv.setAlerttextView(textView);
}
2.DrawView类
image.png
我们需要在该类中接收我们在MainActivity中加入的ImageView视图,我们采取的方法是:在DrawView中加入set,get方法,在MainActivity中进行调用,完成参数的传递
DrawView类中的set方法
image.png
MainActivity中对应的使用方法:
image.png
3.画板的加入:
我们不仅限于旨在两点之间加线,如:
image.png
我们需要当手指滑动到某个位置是,线就画到某个位置,所以我们加入了画板
要画画的话,我们少不了画笔:
private void init(){
paint=new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.YELLOW);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.STROKE);
}
初始化好了之后,我们就可以着手onTouch方法了:
我们利用event.getX()以及event.getY()获取我们当前触摸点,这里很重要,我们判断点是否被点亮的关键就在于该点是否在子控件TextView中
下面是判断触摸点是否在子视图内的方法
关键就在于 该点与每一个子视图的位置关系;
private ImageView viewcontainedPoint(float x,float y){
for(ImageView iv:dotViews){
int[] point=new int[2];
iv.getLocationInWindow(point);
int px=point[0];
int py=point[1];
if((x>=px)&&(x<=px+iv.getWidth())&&(y>=py)&&(y<=py+iv.getHeight())){
return iv;
}
}
return null;
}
onTouch方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
float x=event.getX();
float y=event.getY();
ImageView dot;//判断是否选中的点
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
dot=viewcontainedPoint(x,y);
if(dot!=null){
dot.setVisibility(VISIBLE);
startPoint=new Point((int)(dot.getPivotX()+dot.getX()),(int)(dot.getPivotY()+dot.getY()));
selectedViews.add(dot);
}
break;
case MotionEvent.ACTION_MOVE:
dot = viewcontainedPoint(x,y);
if (dot == null){
//划线
endPoint = new Point((int)x,(int)y);
//刷新
invalidate();
}else{
//判断是第一个点还是其他
if (startPoint == null){
//第一个点
dot.setVisibility(VISIBLE);
selectedViews.add(dot);
//设置起始点
startPoint = new Point((int)(dot.getPivotX() + dot.getX()),
(int)(dot.getPivotY() + dot.getY()));
}else{
//点亮点
dot.setVisibility(VISIBLE);
//在之前和现在的点之间产生一个path
Path path = new Path();
path.moveTo(startPoint.x,startPoint.y);
path.lineTo(dot.getPivotX() + dot.getX(),
dot.getPivotY() + dot.getY());
if(!selectedViews.contains(dot)){
selectedViews.add(dot);
}
paths.add(path);
//当前这个这个点就是起始点
//设置起始点
startPoint = new Point((int)(dot.getPivotX() + dot.getX()),
(int)(dot.getPivotY() + dot.getY()));
//刷新
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
savePassword();
clear();
break;
}
return true;
}
注意:由于在滑动过程中 当前点只与上一个点直接画直线,所以我们“上一个点”需要不断地更新
最后就是密码的保存问题了:
由于我们是利用控件的Tag值来组合成密码,所以我们选择SharedPreferences存储,
关于SharedPreferences,可以去看https://blog.csdn.net/u013441613/article/details/80361817
比较详细了;
密码的保存,与“痕迹”的清除
private void clear(){
for(ImageView dot:selectedViews){
dot.setVisibility(INVISIBLE);
}
selectedViews.clear();
paths.clear();
password.clear();
startPoint=null;
endPoint=null;
invalidate();
}
private void savePassword(){
for(ImageView dot:selectedViews){
//password.add((String) dot.getTag());
password.add(dot.getTag().toString());
}
String orgpassword=sp.getString("password",null);
if(orgpassword==null){//第一次———需要保存密码
if(firstpassword==null){
firstpassword=password.toString();
alerttextView.setText("请再次输入密码");
}else {
if(firstpassword.equals(password.toString())){
editor.putString("password", String.valueOf(password));
editor.commit();
alerttextView.setText("密码保存成功");
}else{
alerttextView.setText("两次密码不一致,请重新输入");
firstpassword=null;
}
}
}else{//不是第一次——判断密码是否正确
if(orgpassword.equals(password.toString())){
alerttextView.setText("密码正确");
}else{
alerttextView.setText("密码错误");
}
}
}
最后加入一个小思考:我们在组合成密码时,由于Move方法会被执行很多次,也就是说当我们在某一个控件上移动时(此时触摸位置并没有离开该控件),密码中会组合很多重复的数字,也就是在Move方法被执行了很多次,我们目前的解决方法是加入一个判断,当该点没有被加入密码中时,则加入密码。
但是当我们需要有重复密码时:或许我们可以判断滑动的方法来判断是否加入密码,即如果我们从外向里滑入控件时,我们选择加入密码,若是从里向外或者只是在里面滑动,我们就可以不加入密码;
贴上所有的代码:
XML:
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:id="@+id/rl_root">
android:id="@+id/iv_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/bg"
android:scaleType="center"/>
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_marginTop="100dp"
android:layout_marginLeft="120dp"
android:textColor="#0f0"
android:textSize="20dp"
android:id="@+id/tv_alertText"/>
MainActivity:
public class MainActivity extends AppCompatActivity {
RelativeLayout rl;
ArrayList dotViews;
DrawView dv;
float padding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dotViews=new ArrayList<>();
setContentView(R.layout.activity_main);
//初始化9个正常点
initNineDot(R.drawable.normal, View.VISIBLE,false);
initDrawView();
//初始化9个高光点
initNineDot(R.drawable.selected,View.INVISIBLE,true);
//将所以点的数组 转递给DrawView
dv.setDotViews(dotViews);
TextView textView=findViewById(R.id.tv_alertText);
dv.setAlerttextView(textView);
}
private void initNineDot(int res,int visiable,boolean record) {
rl = findViewById(R.id.rl_root);
padding = pixelFromDp(40);
//计算两点中心点之间的距离
Point p = new Point();
getWindowManager().getDefaultDisplay().getSize(p);
//获取图片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.normal);
float space = (p.x - 2 * padding - bitmap.getWidth()) / 2;
//计算起始点的坐标
float x = padding;
float y = p.y / 2 - space - bitmap.getHeight();
if (!record) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
creatDot(res, (int) (y + space * i), (int) (x + space * j), visiable,0);
}
}
}else{
int index=1;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
creatDot(res, (int) (y + space * i), (int) (x + space * j), visiable,index);
index++;
}
}
}
}
private void initDrawView(){
dv=new DrawView(this);
RelativeLayout.LayoutParams params=new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
rl.addView(dv);
}
private float pixelFromDp(float size){
return size= size*getResources().getDisplayMetrics().density;
}
private void creatDot(int res,int top,int left,int visiable,int index){
ImageView selected=new ImageView(this);
selected.setBackgroundResource(res);
selected.setTag(index);
RelativeLayout.LayoutParams parmas=new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
parmas.leftMargin= left;
parmas.topMargin= top;
selected.setVisibility(visiable);
rl.addView(selected,parmas);
//判断res是否是selected 则要加入ImageView数组中
if(res==R.drawable.selected){
dotViews.add(selected);
}
}
}
DrawView:
public class DrawView extends View {
private ArrayList dotViews;
Point startPoint,endPoint;
Paint paint;
private ArrayList paths;
private ArrayList selectedViews;
private ArrayList password;
String firstpassword=null;
private TextView alerttextView;
SharedPreferences sp;
SharedPreferences.Editor editor;
public DrawView(Context context) {
super(context);
//setBackgroundColor(Color.YELLOW);
init();
}
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private void init(){
paint=new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.YELLOW);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.STROKE);
paths = new ArrayList<>();
selectedViews=new ArrayList<>();
password=new ArrayList<>();
sp=getContext().getSharedPreferences("password",Context.MODE_PRIVATE);
editor=sp.edit();
}
public ArrayList getDotViews() {
return dotViews;
}
public void setAlerttextView(TextView alerttextView) {
this.alerttextView = alerttextView;
}
public void setDotViews(ArrayList dotViews) {
this.dotViews = dotViews;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
judgment();
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x=event.getX();
float y=event.getY();
ImageView dot;//判断是否选中的点
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
dot=viewcontainedPoint(x,y);
if(dot!=null){
dot.setVisibility(VISIBLE);
startPoint=new Point((int)(dot.getPivotX()+dot.getX()),(int)(dot.getPivotY()+dot.getY()));
selectedViews.add(dot);
}
break;
case MotionEvent.ACTION_MOVE:
dot = viewcontainedPoint(x,y);
if (dot == null){
//划线
endPoint = new Point((int)x,(int)y);
//刷新
invalidate();
}else{
//判断是第一个点还是其他
if (startPoint == null){
//第一个点
dot.setVisibility(VISIBLE);
selectedViews.add(dot);
//设置起始点
startPoint = new Point((int)(dot.getPivotX() + dot.getX()),
(int)(dot.getPivotY() + dot.getY()));
}else{
//点亮点
dot.setVisibility(VISIBLE);
//在之前和现在的点之间产生一个path
Path path = new Path();
path.moveTo(startPoint.x,startPoint.y);
path.lineTo(dot.getPivotX() + dot.getX(),
dot.getPivotY() + dot.getY());
if(!selectedViews.contains(dot)){
selectedViews.add(dot);
}
paths.add(path);
//当前这个这个点就是起始点
//设置起始点
startPoint = new Point((int)(dot.getPivotX() + dot.getX()),
(int)(dot.getPivotY() + dot.getY()));
//刷新
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
savePassword();
clear();
break;
}
return true;
}
//判断触摸点是否在某个dot内
private ImageView viewcontainedPoint(float x,float y){
for(ImageView iv:dotViews){
int[] point=new int[2];
iv.getLocationInWindow(point);
int px=point[0];
int py=point[1];
if((x>=px)&&(x<=px+iv.getWidth())&&(y>=py)&&(y<=py+iv.getHeight())){
return iv;
}
}
return null;
}
@Override
protected void onDraw(Canvas canvas) {
if (paths.size() > 0){
for (Path path: paths){
canvas.drawPath(path, paint);
}
}
if (startPoint != null && endPoint != null) {
canvas.drawLine(startPoint.x, startPoint.y,
endPoint.x, endPoint.y, paint);
}
}
private void clear(){
for(ImageView dot:selectedViews){
dot.setVisibility(INVISIBLE);
}
selectedViews.clear();
paths.clear();
password.clear();
startPoint=null;
endPoint=null;
invalidate();
}
private void savePassword(){
for(ImageView dot:selectedViews){
//password.add((String) dot.getTag());
password.add(dot.getTag().toString());
}
String orgpassword=sp.getString("password",null);
if(orgpassword==null){//第一次———需要保存密码
if(firstpassword==null){
firstpassword=password.toString();
alerttextView.setText("请再次输入密码");
}else {
if(firstpassword.equals(password.toString())){
editor.putString("password", String.valueOf(password));
editor.commit();
alerttextView.setText("密码保存成功");
}else{
alerttextView.setText("两次密码不一致,请重新输入");
firstpassword=null;
}
}
}else{//不是第一次——判断密码是否正确
if(orgpassword.equals(password.toString())){
alerttextView.setText("密码正确");
}else{
alerttextView.setText("密码错误");
}
}
}
private void judgment(){
String orgpassword=sp.getString("password",null);
if(orgpassword==null){
alerttextView.setText("请绘制密码");
}
}
//修改密码
private void changePassword(){
editor.clear();
}
}